Init project
This commit is contained in:
95
.gitignore
vendored
Normal file
95
.gitignore
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
# --- OS / Editor ---
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.swp
|
||||
*.swo
|
||||
*.orig
|
||||
*.tmp
|
||||
*.bak
|
||||
.idea/
|
||||
.vscode/
|
||||
*.code-workspace
|
||||
|
||||
# --- Logs ---
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# --- Env / Secrets ---
|
||||
.env
|
||||
.env.*
|
||||
*.env
|
||||
*.secret
|
||||
*.key
|
||||
*.pem
|
||||
|
||||
# --- Node / Angular ---
|
||||
node_modules/
|
||||
frontend/node_modules/
|
||||
frontend/dist/
|
||||
frontend/.angular/
|
||||
frontend/.cache/
|
||||
coverage/
|
||||
.browserslistrc*
|
||||
|
||||
# --- Python (doc-service) ---
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
*.pytest_cache/
|
||||
.mypy_cache/
|
||||
.ruff_cache/
|
||||
.venv/
|
||||
venv/
|
||||
.envrc
|
||||
.docz/
|
||||
# Outputs
|
||||
doc-service/app/output/
|
||||
|
||||
# --- Go (core-service) ---
|
||||
core-service/bin/
|
||||
core-service/tmp/
|
||||
*.exe
|
||||
*.test
|
||||
*.out
|
||||
# Go coverage
|
||||
coverage.out
|
||||
|
||||
# --- Protobuf generated (optional ignore) ---
|
||||
core-service/proto/**/*.pb.go
|
||||
# Uncomment if generating stubs for Python/TS
|
||||
# doc-service/app/proto/**
|
||||
# frontend/src/app/proto/**
|
||||
|
||||
# --- Docker / Compose ---
|
||||
**/.docker/
|
||||
.dockerignore
|
||||
# Local data mounts (if any)
|
||||
docker/postgres/data/
|
||||
|
||||
# --- Build artifacts ---
|
||||
dist/
|
||||
build/
|
||||
.tmp/
|
||||
.temp/
|
||||
|
||||
# --- Misc ---
|
||||
*.iml
|
||||
*.DS_Store?
|
||||
.sass-cache/
|
||||
CACHE/
|
||||
.cache/
|
||||
.npm/
|
||||
.parcel-cache/
|
||||
|
||||
# --- Reports ---
|
||||
reports/
|
||||
*.lcov
|
||||
|
||||
# --- OS Trash ---
|
||||
$RECYCLE.BIN/
|
||||
*.Trash-*/
|
||||
226
PROJECT_STRUCTURE.md
Normal file
226
PROJECT_STRUCTURE.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# 📁 Структура проекта ERP MVP
|
||||
|
||||
```
|
||||
erp-mvp/
|
||||
├── 📁 core-service/ # Go Backend (Core API)
|
||||
│ ├── 📁 cmd/
|
||||
│ │ └── main.go # Точка входа приложения
|
||||
│ ├── 📁 internal/
|
||||
│ │ ├── 📁 api/ # HTTP API handlers
|
||||
│ │ ├── 📁 config/ # Конфигурация
|
||||
│ │ ├── 📁 database/ # Работа с БД
|
||||
│ │ ├── 📁 grpc/ # gRPC клиенты/серверы
|
||||
│ │ ├── 📁 logger/ # Логирование
|
||||
│ │ ├── 📁 models/ # Модели данных
|
||||
│ │ ├── 📁 redis/ # Redis клиент
|
||||
│ │ └── 📁 services/ # Бизнес-логика
|
||||
│ ├── 📁 pkg/ # Публичные пакеты
|
||||
│ ├── 📁 proto/ # Сгенерированные Protocol Buffers
|
||||
│ ├── go.mod # Go зависимости
|
||||
│ ├── go.sum # Go checksums
|
||||
│ └── Dockerfile # Docker образ
|
||||
│
|
||||
├── 📁 doc-service/ # Python Document Service
|
||||
│ ├── 📁 app/
|
||||
│ │ ├── 📁 api/ # FastAPI роуты
|
||||
│ │ │ ├── routes/
|
||||
│ │ │ └── middleware/
|
||||
│ │ ├── 📁 core/ # Основная логика
|
||||
│ │ │ ├── redis_client.py
|
||||
│ │ │ └── logging.py
|
||||
│ │ ├── 📁 models/ # Pydantic модели
|
||||
│ │ │ └── document.py
|
||||
│ │ ├── 📁 services/ # Сервисы генерации документов
|
||||
│ │ │ ├── pdf_service.py
|
||||
│ │ │ ├── excel_service.py
|
||||
│ │ │ └── qr_service.py
|
||||
│ │ ├── 📁 templates/ # Jinja2 шаблоны
|
||||
│ │ ├── config.py # Конфигурация
|
||||
│ │ └── main.py # Точка входа
|
||||
│ ├── 📁 output/ # Генерируемые документы
|
||||
│ ├── requirements.txt # Python зависимости
|
||||
│ └── Dockerfile # Docker образ
|
||||
│
|
||||
├── 📁 frontend/ # Angular PWA Frontend
|
||||
│ ├── 📁 src/
|
||||
│ │ ├── 📁 app/
|
||||
│ │ │ ├── 📁 components/ # Angular компоненты
|
||||
│ │ │ │ ├── header/
|
||||
│ │ │ │ ├── qr-scanner/
|
||||
│ │ │ │ ├── location-details/
|
||||
│ │ │ │ ├── item-list/
|
||||
│ │ │ │ └── search/
|
||||
│ │ │ ├── 📁 services/ # Angular сервисы
|
||||
│ │ │ │ ├── api.service.ts
|
||||
│ │ │ │ ├── auth.service.ts
|
||||
│ │ │ │ └── auth.interceptor.ts
|
||||
│ │ │ ├── 📁 store/ # NgRx store
|
||||
│ │ │ │ ├── app.reducers.ts
|
||||
│ │ │ │ ├── app.effects.ts
|
||||
│ │ │ │ └── app.actions.ts
|
||||
│ │ │ ├── 📁 models/ # TypeScript модели
|
||||
│ │ │ ├── app.component.ts
|
||||
│ │ │ ├── app.module.ts
|
||||
│ │ │ └── app-routing.module.ts
|
||||
│ │ ├── 📁 assets/ # Статические ресурсы
|
||||
│ │ ├── 📁 environments/ # Конфигурация окружений
|
||||
│ │ ├── index.html
|
||||
│ │ ├── main.ts
|
||||
│ │ ├── styles.scss
|
||||
│ │ └── manifest.webmanifest # PWA манифест
|
||||
│ ├── package.json # Node.js зависимости
|
||||
│ ├── angular.json # Angular конфигурация
|
||||
│ ├── nginx.conf # Nginx конфигурация
|
||||
│ └── Dockerfile # Docker образ
|
||||
│
|
||||
├── 📁 proto/ # Protocol Buffers
|
||||
│ ├── core.proto # Core Service API
|
||||
│ ├── document.proto # Document Service API
|
||||
│ └── events.proto # Event-driven сообщения
|
||||
│
|
||||
├── 📁 docker/ # Docker конфигурации
|
||||
│ ├── 📁 postgres/
|
||||
│ │ └── init.sql # Инициализация БД
|
||||
│ ├── 📁 prometheus/
|
||||
│ │ └── prometheus.yml # Prometheus конфигурация
|
||||
│ └── 📁 grafana/
|
||||
│ └── provisioning/ # Grafana дашборды
|
||||
│
|
||||
├── docker-compose.yml # Основной compose файл
|
||||
├── README.md # Основная документация
|
||||
├── STARTUP.md # Инструкции по запуску
|
||||
└── PROJECT_STRUCTURE.md # Этот файл
|
||||
```
|
||||
|
||||
## 🔧 Технологический стек
|
||||
|
||||
### Core Service (Go)
|
||||
- **Framework:** Gin (HTTP сервер)
|
||||
- **Database:** PostgreSQL (основная БД)
|
||||
- **Cache:** Redis (кэширование)
|
||||
- **Auth:** JWT (аутентификация)
|
||||
- **gRPC:** Protocol Buffers (межсервисная коммуникация)
|
||||
- **Logging:** logrus/zerolog (структурированное логирование)
|
||||
- **Monitoring:** Prometheus (метрики)
|
||||
|
||||
### Document Service (Python)
|
||||
- **Framework:** FastAPI (веб-фреймворк)
|
||||
- **PDF:** reportlab, weasyprint (генерация PDF)
|
||||
- **Excel:** openpyxl (работа с Excel)
|
||||
- **Word:** python-docx (работа с Word)
|
||||
- **QR:** qrcode (генерация QR-кодов)
|
||||
- **Templates:** Jinja2 (шаблонизация)
|
||||
- **Cache:** Redis (кэширование документов)
|
||||
- **gRPC:** grpcio (межсервисная коммуникация)
|
||||
|
||||
### Frontend (Angular)
|
||||
- **Framework:** Angular 17+ (SPA фреймворк)
|
||||
- **PWA:** @angular/service-worker (Progressive Web App)
|
||||
- **UI:** Angular Material (компоненты UI)
|
||||
- **Styling:** Tailwind CSS (утилитарные стили)
|
||||
- **QR Scanner:** @zxing/ngx-scanner (сканирование QR)
|
||||
- **State:** NgRx (управление состоянием)
|
||||
- **HTTP:** HttpClient (API запросы)
|
||||
|
||||
### Infrastructure
|
||||
- **Containerization:** Docker + Docker Compose
|
||||
- **Database:** PostgreSQL 15 (основная БД)
|
||||
- **Cache:** Redis 7 (кэширование)
|
||||
- **Gateway:** Traefik (опционально)
|
||||
- **Monitoring:** Prometheus + Grafana (опционально)
|
||||
- **Web Server:** Nginx (для frontend)
|
||||
|
||||
## 🚀 Архитектура
|
||||
|
||||
### Микросервисная архитектура
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Angular PWA │ │ Go Core │ │ PostgreSQL │
|
||||
│ (Frontend) │◄──►│ Service │◄──►│ (Database) │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ QR Scanner │ │ Python Doc │
|
||||
│ (Camera) │ │ Service │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Redis Cache │
|
||||
│ (Documents) │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
### Коммуникация между сервисами
|
||||
- **Frontend ↔ Core Service:** REST API (HTTP/JSON)
|
||||
- **Frontend ↔ Document Service:** REST API (HTTP/JSON)
|
||||
- **Core Service ↔ Document Service:** gRPC (Protocol Buffers)
|
||||
- **Все сервисы ↔ Redis:** Redis Protocol (кэширование)
|
||||
|
||||
## 📊 База данных
|
||||
|
||||
### Основные таблицы
|
||||
- **organizations** - Организации/компании
|
||||
- **users** - Пользователи системы
|
||||
- **storage_locations** - Места хранения
|
||||
- **items** - Товары/материалы
|
||||
- **item_placements** - Размещение товаров
|
||||
- **operations** - Операции с товарами
|
||||
|
||||
### Индексы
|
||||
- Организация-scope индексы для всех таблиц
|
||||
- Индексы по типам и категориям
|
||||
- Временные индексы для аналитики
|
||||
|
||||
## 🔐 Безопасность
|
||||
|
||||
### Аутентификация
|
||||
- JWT токены с refresh механизмом
|
||||
- Organization-scope на всех данных
|
||||
- Middleware для проверки прав доступа
|
||||
|
||||
### Валидация
|
||||
- Входная валидация всех параметров
|
||||
- SQL injection protection
|
||||
- XSS protection в frontend
|
||||
|
||||
### HTTPS
|
||||
- Обязательное использование HTTPS в продакшене
|
||||
- Secure cookies для JWT
|
||||
- CORS настройки для PWA
|
||||
|
||||
## 📱 PWA Особенности
|
||||
|
||||
### Service Worker
|
||||
- Кэширование статических ресурсов
|
||||
- Offline fallback для базовых функций
|
||||
- Background sync для операций
|
||||
|
||||
### QR Scanner
|
||||
- WebRTC для доступа к камере
|
||||
- Real-time распознавание QR-кодов
|
||||
- Fallback на ручной ввод
|
||||
|
||||
### Установка
|
||||
- Manifest для установки как нативное приложение
|
||||
- Splash screen и иконки
|
||||
- Автоматические обновления
|
||||
|
||||
## 🚀 Развертывание
|
||||
|
||||
### Docker Compose
|
||||
- Все сервисы в отдельных контейнерах
|
||||
- Автоматическая инициализация БД
|
||||
- Готовые образы для быстрого старта
|
||||
|
||||
### Мониторинг
|
||||
- Prometheus для сбора метрик
|
||||
- Grafana для визуализации
|
||||
- Structured logging для всех сервисов
|
||||
|
||||
### Масштабирование
|
||||
- Горизонтальное масштабирование сервисов
|
||||
- Load balancing через Traefik
|
||||
- Кэширование через Redis
|
||||
49
README.md
Normal file
49
README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# ERP для мастеров - MVP
|
||||
|
||||
## 🏗️ Архитектура проекта
|
||||
|
||||
```
|
||||
erp-mvp/
|
||||
├── core-service/ # Go backend (Core API)
|
||||
├── doc-service/ # Python document service
|
||||
├── frontend/ # Angular PWA frontend
|
||||
├── proto/ # Shared Protocol Buffers
|
||||
├── docker/ # Docker configurations
|
||||
└── docker-compose.yml # Main deployment file
|
||||
```
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
### Требования
|
||||
- Docker & Docker Compose
|
||||
- Go 1.21+
|
||||
- Node.js 18+
|
||||
- Python 3.11+
|
||||
|
||||
### Запуск
|
||||
```bash
|
||||
# Клонирование и настройка
|
||||
git clone <repository>
|
||||
cd erp-mvp
|
||||
|
||||
# Запуск всех сервисов
|
||||
docker-compose up -d
|
||||
|
||||
# Или разработка локально
|
||||
cd core-service && go run cmd/main.go
|
||||
cd doc-service && python -m uvicorn app.main:app --reload
|
||||
cd frontend && npm start
|
||||
```
|
||||
|
||||
## 📚 Документация
|
||||
|
||||
- [Архитектура MVP](second-mind-aep/💡%20Идеи/💡%20Проекты/ERP%20для%20малых%20производств/Архитектура-MVP.md)
|
||||
- [Техническое задание](second-mind-aep/💡%20Идеи/💡%20Проекты/ERP%20для%20малых%20производств/Техническое-задание-MVP.md)
|
||||
- [MVP План](second-mind-aep/💡%20Идеи/💡%20Проекты/ERP%20для%20малых%20производств/MVP-План.md)
|
||||
|
||||
## 🔧 Технологический стек
|
||||
|
||||
- **Core Service:** Go (Gin) + PostgreSQL + JWT + gRPC
|
||||
- **Document Service:** Python (FastAPI) + Redis + Document libraries
|
||||
- **Frontend:** Angular PWA + Material UI + Tailwind CSS
|
||||
- **Infrastructure:** Docker + Docker Compose + Redis + HTTPS
|
||||
246
STARTUP.md
Normal file
246
STARTUP.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# 🚀 Инструкции по запуску ERP MVP
|
||||
|
||||
## 📋 Требования
|
||||
|
||||
### Системные требования
|
||||
- **Docker** 20.10+
|
||||
- **Docker Compose** 2.0+
|
||||
- **Git** 2.30+
|
||||
|
||||
### Минимальные ресурсы
|
||||
- **RAM:** 4 GB
|
||||
- **CPU:** 2 ядра
|
||||
- **Диск:** 10 GB свободного места
|
||||
|
||||
## 🔧 Быстрый запуск
|
||||
|
||||
### 1. Клонирование репозитория
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd erp-mvp
|
||||
```
|
||||
|
||||
### 2. Настройка переменных окружения
|
||||
```bash
|
||||
# Создание .env файла для Core Service
|
||||
cp core-service/.env.example core-service/.env
|
||||
|
||||
# Создание .env файла для Document Service
|
||||
cp doc-service/.env.example doc-service/.env
|
||||
|
||||
# Редактирование переменных (опционально)
|
||||
nano core-service/.env
|
||||
nano doc-service/.env
|
||||
```
|
||||
|
||||
### 3. Запуск всех сервисов
|
||||
```bash
|
||||
# Сборка и запуск всех контейнеров
|
||||
docker-compose up -d
|
||||
|
||||
# Просмотр логов
|
||||
docker-compose logs -f
|
||||
|
||||
# Проверка статуса сервисов
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
### 4. Проверка работоспособности
|
||||
```bash
|
||||
# Core Service API
|
||||
curl http://localhost:8080/health
|
||||
|
||||
# Document Service API
|
||||
curl http://localhost:8000/health
|
||||
|
||||
# Frontend
|
||||
curl http://localhost:3000/health
|
||||
```
|
||||
|
||||
## 🌐 Доступ к сервисам
|
||||
|
||||
После успешного запуска:
|
||||
|
||||
- **Frontend (Angular PWA):** http://localhost:3000
|
||||
- **Core Service API:** http://localhost:8080
|
||||
- **Document Service API:** http://localhost:8000
|
||||
- **API Documentation:** http://localhost:8000/docs
|
||||
- **PostgreSQL:** localhost:5432
|
||||
- **Redis:** localhost:6379
|
||||
|
||||
## 🔍 Мониторинг (опционально)
|
||||
|
||||
### Запуск с мониторингом
|
||||
```bash
|
||||
# Запуск с Prometheus и Grafana
|
||||
docker-compose --profile monitoring up -d
|
||||
|
||||
# Доступ к Grafana: http://localhost:3001
|
||||
# Логин: admin / admin
|
||||
```
|
||||
|
||||
### Запуск с API Gateway
|
||||
```bash
|
||||
# Запуск с Traefik
|
||||
docker-compose --profile gateway up -d
|
||||
|
||||
# Traefik Dashboard: http://localhost:8081
|
||||
```
|
||||
|
||||
## 🛠️ Разработка
|
||||
|
||||
### Локальная разработка
|
||||
|
||||
#### Core Service (Go)
|
||||
```bash
|
||||
cd core-service
|
||||
go mod download
|
||||
go run cmd/main.go
|
||||
```
|
||||
|
||||
#### Document Service (Python)
|
||||
```bash
|
||||
cd doc-service
|
||||
pip install -r requirements.txt
|
||||
python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
#### Frontend (Angular)
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
### Горячая перезагрузка
|
||||
```bash
|
||||
# Пересборка и перезапуск конкретного сервиса
|
||||
docker-compose up -d --build core-service
|
||||
|
||||
# Пересборка всех сервисов
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
## 🗄️ База данных
|
||||
|
||||
### Подключение к PostgreSQL
|
||||
```bash
|
||||
# Через Docker
|
||||
docker-compose exec postgres psql -U erp_user -d erp_mvp
|
||||
|
||||
# Или через внешний клиент
|
||||
# Host: localhost
|
||||
# Port: 5432
|
||||
# Database: erp_mvp
|
||||
# Username: erp_user
|
||||
# Password: erp_pass
|
||||
```
|
||||
|
||||
### Миграции
|
||||
```bash
|
||||
# Применение миграций (если есть)
|
||||
docker-compose exec core-service ./migrate up
|
||||
```
|
||||
|
||||
## 🔐 Безопасность
|
||||
|
||||
### Изменение паролей по умолчанию
|
||||
```bash
|
||||
# В docker-compose.yml измените:
|
||||
# - POSTGRES_PASSWORD
|
||||
# - JWT_SECRET
|
||||
# - REDIS_PASSWORD (если используется)
|
||||
```
|
||||
|
||||
### HTTPS (для продакшена)
|
||||
```bash
|
||||
# Добавьте SSL сертификаты
|
||||
# Настройте Traefik для HTTPS
|
||||
# Обновите переменные окружения
|
||||
```
|
||||
|
||||
## 🐛 Устранение неполадок
|
||||
|
||||
### Просмотр логов
|
||||
```bash
|
||||
# Все сервисы
|
||||
docker-compose logs
|
||||
|
||||
# Конкретный сервис
|
||||
docker-compose logs core-service
|
||||
docker-compose logs doc-service
|
||||
docker-compose logs frontend
|
||||
|
||||
# Следить за логами в реальном времени
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
### Перезапуск сервисов
|
||||
```bash
|
||||
# Перезапуск конкретного сервиса
|
||||
docker-compose restart core-service
|
||||
|
||||
# Перезапуск всех сервисов
|
||||
docker-compose restart
|
||||
```
|
||||
|
||||
### Очистка данных
|
||||
```bash
|
||||
# Остановка и удаление контейнеров
|
||||
docker-compose down
|
||||
|
||||
# Удаление с данными
|
||||
docker-compose down -v
|
||||
|
||||
# Полная очистка
|
||||
docker-compose down -v --rmi all
|
||||
```
|
||||
|
||||
## 📊 Проверка работоспособности
|
||||
|
||||
### Тестовые запросы
|
||||
```bash
|
||||
# Проверка Core Service
|
||||
curl -X GET http://localhost:8080/health
|
||||
|
||||
# Проверка Document Service
|
||||
curl -X GET http://localhost:8000/health
|
||||
|
||||
# Проверка Frontend
|
||||
curl -X GET http://localhost:3000/health
|
||||
```
|
||||
|
||||
### Проверка базы данных
|
||||
```bash
|
||||
# Подключение к PostgreSQL
|
||||
docker-compose exec postgres psql -U erp_user -d erp_mvp -c "SELECT version();"
|
||||
|
||||
# Проверка таблиц
|
||||
docker-compose exec postgres psql -U erp_user -d erp_mvp -c "\dt"
|
||||
```
|
||||
|
||||
## 🚀 Продакшен развертывание
|
||||
|
||||
### Подготовка к продакшену
|
||||
1. Измените все пароли по умолчанию
|
||||
2. Настройте SSL сертификаты
|
||||
3. Настройте мониторинг
|
||||
4. Настройте бэкапы
|
||||
5. Обновите переменные окружения
|
||||
|
||||
### Команды для продакшена
|
||||
```bash
|
||||
# Сборка оптимизированных образов
|
||||
docker-compose -f docker-compose.yml -f docker-compose.prod.yml build
|
||||
|
||||
# Запуск в продакшене
|
||||
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
## 📞 Поддержка
|
||||
|
||||
При возникновении проблем:
|
||||
1. Проверьте логи: `docker-compose logs`
|
||||
2. Убедитесь, что все порты свободны
|
||||
3. Проверьте системные ресурсы
|
||||
4. Обратитесь к документации проекта
|
||||
51
core-service/Dockerfile
Normal file
51
core-service/Dockerfile
Normal file
@@ -0,0 +1,51 @@
|
||||
# Многоэтапная сборка для Go приложения
|
||||
FROM golang:1.21-alpine AS builder
|
||||
|
||||
# Установка зависимостей для сборки
|
||||
RUN apk add --no-cache git ca-certificates tzdata
|
||||
|
||||
# Установка рабочей директории
|
||||
WORKDIR /app
|
||||
|
||||
# Копирование go mod файлов
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
# Скачивание зависимостей
|
||||
RUN go mod download
|
||||
|
||||
# Копирование исходного кода
|
||||
COPY . .
|
||||
|
||||
# Сборка приложения
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/main.go
|
||||
|
||||
# Финальный образ
|
||||
FROM alpine:latest
|
||||
|
||||
# Установка ca-certificates для HTTPS запросов
|
||||
RUN apk --no-cache add ca-certificates tzdata
|
||||
|
||||
# Создание пользователя для безопасности
|
||||
RUN addgroup -g 1001 -S appgroup && \
|
||||
adduser -u 1001 -S appuser -G appgroup
|
||||
|
||||
# Установка рабочей директории
|
||||
WORKDIR /root/
|
||||
|
||||
# Копирование бинарного файла из builder
|
||||
COPY --from=builder /app/main .
|
||||
|
||||
# Копирование конфигурационных файлов
|
||||
COPY --from=builder /app/config ./config
|
||||
|
||||
# Смена владельца файлов
|
||||
RUN chown -R appuser:appgroup /root/
|
||||
|
||||
# Переключение на непривилегированного пользователя
|
||||
USER appuser
|
||||
|
||||
# Экспорт порта
|
||||
EXPOSE 8080
|
||||
|
||||
# Команда запуска
|
||||
CMD ["./main"]
|
||||
77
core-service/cmd/main.go
Normal file
77
core-service/cmd/main.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"erp-mvp/core-service/internal/api"
|
||||
"erp-mvp/core-service/internal/config"
|
||||
"erp-mvp/core-service/internal/database"
|
||||
"erp-mvp/core-service/internal/grpc"
|
||||
"erp-mvp/core-service/internal/logger"
|
||||
"erp-mvp/core-service/internal/redis"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Инициализация логгера
|
||||
logger := logger.New()
|
||||
|
||||
// Загрузка конфигурации
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to load config", err)
|
||||
}
|
||||
|
||||
// Подключение к базе данных
|
||||
db, err := database.Connect(cfg.Database)
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to connect to database", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Подключение к Redis
|
||||
redisClient, err := redis.Connect(cfg.Redis)
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to connect to Redis", err)
|
||||
}
|
||||
defer redisClient.Close()
|
||||
|
||||
// Инициализация gRPC клиента для Document Service
|
||||
grpcClient, err := grpc.NewDocumentServiceClient(cfg.DocumentService.URL)
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to connect to Document Service", err)
|
||||
}
|
||||
defer grpcClient.Close()
|
||||
|
||||
// Создание API сервера
|
||||
server := api.NewServer(cfg, db, redisClient, grpcClient, logger)
|
||||
|
||||
// Запуск HTTP сервера
|
||||
go func() {
|
||||
logger.Info("Starting HTTP server on", cfg.Server.Port)
|
||||
if err := server.Start(); err != nil && err != http.ErrServerClosed {
|
||||
logger.Fatal("Failed to start server", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Graceful shutdown
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
|
||||
logger.Info("Shutting down server...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := server.Shutdown(ctx); err != nil {
|
||||
logger.Fatal("Server forced to shutdown", err)
|
||||
}
|
||||
|
||||
logger.Info("Server exited")
|
||||
}
|
||||
47
core-service/go.mod
Normal file
47
core-service/go.mod
Normal file
@@ -0,0 +1,47 @@
|
||||
module erp-mvp/core-service
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/go-playground/validator/v10 v10.15.5
|
||||
google.golang.org/grpc v1.58.0
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/redis/go-redis/v9 v9.2.1
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/joho/godotenv v1.4.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/prometheus/client_golang v1.17.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.12.0 // indirect
|
||||
golang.org/x/net v0.14.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
103
core-service/internal/config/config.go
Normal file
103
core-service/internal/config/config.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig
|
||||
Database DatabaseConfig
|
||||
Redis RedisConfig
|
||||
JWT JWTConfig
|
||||
DocumentService DocumentServiceConfig
|
||||
Log LogConfig
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port string
|
||||
Host string
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Host string
|
||||
Port string
|
||||
User string
|
||||
Password string
|
||||
DBName string
|
||||
SSLMode string
|
||||
}
|
||||
|
||||
type RedisConfig struct {
|
||||
Host string
|
||||
Port string
|
||||
Password string
|
||||
DB int
|
||||
}
|
||||
|
||||
type JWTConfig struct {
|
||||
Secret string
|
||||
Expiration int // в часах
|
||||
}
|
||||
|
||||
type DocumentServiceConfig struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Level string
|
||||
}
|
||||
|
||||
func Load() (*Config, error) {
|
||||
// Загрузка .env файла если существует
|
||||
godotenv.Load()
|
||||
|
||||
return &Config{
|
||||
Server: ServerConfig{
|
||||
Port: getEnv("SERVER_PORT", "8080"),
|
||||
Host: getEnv("SERVER_HOST", "0.0.0.0"),
|
||||
},
|
||||
Database: DatabaseConfig{
|
||||
Host: getEnv("DB_HOST", "localhost"),
|
||||
Port: getEnv("DB_PORT", "5432"),
|
||||
User: getEnv("DB_USER", "erp_user"),
|
||||
Password: getEnv("DB_PASSWORD", "erp_pass"),
|
||||
DBName: getEnv("DB_NAME", "erp_mvp"),
|
||||
SSLMode: getEnv("DB_SSLMODE", "disable"),
|
||||
},
|
||||
Redis: RedisConfig{
|
||||
Host: getEnv("REDIS_HOST", "localhost"),
|
||||
Port: getEnv("REDIS_PORT", "6379"),
|
||||
Password: getEnv("REDIS_PASSWORD", ""),
|
||||
DB: getEnvAsInt("REDIS_DB", 0),
|
||||
},
|
||||
JWT: JWTConfig{
|
||||
Secret: getEnv("JWT_SECRET", "your-secret-key"),
|
||||
Expiration: getEnvAsInt("JWT_EXPIRATION", 24),
|
||||
},
|
||||
DocumentService: DocumentServiceConfig{
|
||||
URL: getEnv("DOC_SERVICE_URL", "http://localhost:8000"),
|
||||
},
|
||||
Log: LogConfig{
|
||||
Level: getEnv("LOG_LEVEL", "info"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func getEnvAsInt(key string, defaultValue int) int {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
if intValue, err := strconv.Atoi(value); err == nil {
|
||||
return intValue
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
120
core-service/internal/models/models.go
Normal file
120
core-service/internal/models/models.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Organization представляет организацию/компанию
|
||||
type Organization struct {
|
||||
ID uuid.UUID `json:"id" db:"id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Type string `json:"type" db:"type"`
|
||||
Settings map[string]any `json:"settings" db:"settings"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
// User представляет пользователя системы
|
||||
type User struct {
|
||||
ID uuid.UUID `json:"id" db:"id"`
|
||||
OrganizationID uuid.UUID `json:"organization_id" db:"organization_id"`
|
||||
Email string `json:"email" db:"email"`
|
||||
Role string `json:"role" db:"role"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
// StorageLocation представляет место хранения
|
||||
type StorageLocation struct {
|
||||
ID uuid.UUID `json:"id" db:"id"`
|
||||
OrganizationID uuid.UUID `json:"organization_id" db:"organization_id"`
|
||||
ParentID *uuid.UUID `json:"parent_id" db:"parent_id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Address string `json:"address" db:"address"`
|
||||
Type string `json:"type" db:"type"`
|
||||
Coordinates map[string]any `json:"coordinates" db:"coordinates"`
|
||||
QRCode string `json:"qr_code" db:"qr_code"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
// Item представляет товар/материал
|
||||
type Item struct {
|
||||
ID uuid.UUID `json:"id" db:"id"`
|
||||
OrganizationID uuid.UUID `json:"organization_id" db:"organization_id"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Description string `json:"description" db:"description"`
|
||||
Category string `json:"category" db:"category"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
// ItemPlacement представляет размещение товара в месте хранения
|
||||
type ItemPlacement struct {
|
||||
ID uuid.UUID `json:"id" db:"id"`
|
||||
OrganizationID uuid.UUID `json:"organization_id" db:"organization_id"`
|
||||
ItemID uuid.UUID `json:"item_id" db:"item_id"`
|
||||
LocationID uuid.UUID `json:"location_id" db:"location_id"`
|
||||
Quantity int `json:"quantity" db:"quantity"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
// LoginRequest запрос на аутентификацию
|
||||
type LoginRequest struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
// LoginResponse ответ на аутентификацию
|
||||
type LoginResponse struct {
|
||||
Token string `json:"token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
User User `json:"user"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
// CreateLocationRequest запрос на создание места хранения
|
||||
type CreateLocationRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Address string `json:"address" binding:"required"`
|
||||
Type string `json:"type" binding:"required"`
|
||||
ParentID *uuid.UUID `json:"parent_id"`
|
||||
Coordinates map[string]any `json:"coordinates"`
|
||||
}
|
||||
|
||||
// CreateItemRequest запрос на создание товара
|
||||
type CreateItemRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Category string `json:"category" binding:"required"`
|
||||
}
|
||||
|
||||
// PlaceItemRequest запрос на размещение товара
|
||||
type PlaceItemRequest struct {
|
||||
ItemID uuid.UUID `json:"item_id" binding:"required"`
|
||||
LocationID uuid.UUID `json:"location_id" binding:"required"`
|
||||
Quantity int `json:"quantity" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
// SearchRequest запрос на поиск
|
||||
type SearchRequest struct {
|
||||
Query string `json:"query" binding:"required"`
|
||||
Category string `json:"category"`
|
||||
LocationID *uuid.UUID `json:"location_id"`
|
||||
}
|
||||
|
||||
// SearchResponse результат поиска
|
||||
type SearchResponse struct {
|
||||
Items []ItemWithLocation `json:"items"`
|
||||
TotalCount int `json:"total_count"`
|
||||
}
|
||||
|
||||
// ItemWithLocation товар с информацией о месте размещения
|
||||
type ItemWithLocation struct {
|
||||
Item Item `json:"item"`
|
||||
Location StorageLocation `json:"location"`
|
||||
Quantity int `json:"quantity"`
|
||||
}
|
||||
53
doc-service/Dockerfile
Normal file
53
doc-service/Dockerfile
Normal file
@@ -0,0 +1,53 @@
|
||||
# Многоэтапная сборка для Python приложения
|
||||
FROM python:3.11-slim AS builder
|
||||
|
||||
# Установка зависимостей для сборки
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
g++ \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Установка рабочей директории
|
||||
WORKDIR /app
|
||||
|
||||
# Копирование requirements файла
|
||||
COPY requirements.txt .
|
||||
|
||||
# Установка зависимостей Python
|
||||
RUN pip install --no-cache-dir --user -r requirements.txt
|
||||
|
||||
# Финальный образ
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Установка системных зависимостей
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Создание пользователя для безопасности
|
||||
RUN groupadd -r appuser && useradd -r -g appuser appuser
|
||||
|
||||
# Установка рабочей директории
|
||||
WORKDIR /app
|
||||
|
||||
# Копирование Python пакетов из builder
|
||||
COPY --from=builder /root/.local /root/.local
|
||||
|
||||
# Копирование исходного кода
|
||||
COPY . .
|
||||
|
||||
# Создание необходимых директорий
|
||||
RUN mkdir -p /app/templates /app/output && \
|
||||
chown -R appuser:appuser /app
|
||||
|
||||
# Добавление локальных пакетов в PATH
|
||||
ENV PATH=/root/.local/bin:$PATH
|
||||
|
||||
# Переключение на непривилегированного пользователя
|
||||
USER appuser
|
||||
|
||||
# Экспорт порта
|
||||
EXPOSE 8000
|
||||
|
||||
# Команда запуска
|
||||
CMD ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
56
doc-service/app/config.py
Normal file
56
doc-service/app/config.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import os
|
||||
from typing import List
|
||||
from pydantic_settings import BaseSettings
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Загрузка .env файла
|
||||
load_dotenv()
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""Настройки приложения"""
|
||||
|
||||
# Основные настройки
|
||||
DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true"
|
||||
HOST: str = os.getenv("HOST", "0.0.0.0")
|
||||
PORT: int = int(os.getenv("PORT", "8000"))
|
||||
LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
|
||||
|
||||
# CORS
|
||||
ALLOWED_ORIGINS: List[str] = [
|
||||
"http://localhost:3000",
|
||||
"http://localhost:8080",
|
||||
"https://localhost:3000",
|
||||
"https://localhost:8080"
|
||||
]
|
||||
|
||||
# Redis
|
||||
REDIS_HOST: str = os.getenv("REDIS_HOST", "localhost")
|
||||
REDIS_PORT: int = int(os.getenv("REDIS_PORT", "6379"))
|
||||
REDIS_PASSWORD: str = os.getenv("REDIS_PASSWORD", "")
|
||||
REDIS_DB: int = int(os.getenv("REDIS_DB", "0"))
|
||||
|
||||
# Core Service
|
||||
CORE_SERVICE_URL: str = os.getenv("CORE_SERVICE_URL", "http://localhost:8080")
|
||||
|
||||
# Документы
|
||||
DOCUMENTS_CACHE_TTL: int = int(os.getenv("DOCUMENTS_CACHE_TTL", "86400")) # 24 часа
|
||||
MAX_DOCUMENT_SIZE: int = int(os.getenv("MAX_DOCUMENT_SIZE", "10485760")) # 10MB
|
||||
|
||||
# Пути для файлов
|
||||
TEMPLATES_DIR: str = os.getenv("TEMPLATES_DIR", "app/templates")
|
||||
OUTPUT_DIR: str = os.getenv("OUTPUT_DIR", "app/output")
|
||||
|
||||
# QR коды
|
||||
QR_CODE_SIZE: int = int(os.getenv("QR_CODE_SIZE", "10"))
|
||||
QR_CODE_BORDER: int = int(os.getenv("QR_CODE_BORDER", "2"))
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
case_sensitive = False
|
||||
|
||||
# Создание экземпляра настроек
|
||||
settings = Settings()
|
||||
|
||||
# Создание директорий если не существуют
|
||||
os.makedirs(settings.TEMPLATES_DIR, exist_ok=True)
|
||||
os.makedirs(settings.OUTPUT_DIR, exist_ok=True)
|
||||
80
doc-service/app/main.py
Normal file
80
doc-service/app/main.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import uvicorn
|
||||
import structlog
|
||||
|
||||
from app.config import settings
|
||||
from app.api.routes import documents, templates
|
||||
from app.core.redis_client import redis_client
|
||||
from app.core.logging import setup_logging
|
||||
|
||||
# Настройка логирования
|
||||
setup_logging()
|
||||
logger = structlog.get_logger()
|
||||
|
||||
# Создание FastAPI приложения
|
||||
app = FastAPI(
|
||||
title="ERP Document Service",
|
||||
description="Сервис для генерации документов (PDF, Excel, Word)",
|
||||
version="1.0.0",
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc"
|
||||
)
|
||||
|
||||
# Настройка CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.ALLOWED_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Подключение роутов
|
||||
app.include_router(documents.router, prefix="/api/documents", tags=["documents"])
|
||||
app.include_router(templates.router, prefix="/api/templates", tags=["templates"])
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
"""Событие запуска приложения"""
|
||||
logger.info("Starting Document Service")
|
||||
|
||||
# Подключение к Redis
|
||||
await redis_client.connect()
|
||||
logger.info("Connected to Redis")
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown_event():
|
||||
"""Событие остановки приложения"""
|
||||
logger.info("Shutting down Document Service")
|
||||
|
||||
# Отключение от Redis
|
||||
await redis_client.disconnect()
|
||||
logger.info("Disconnected from Redis")
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
"""Проверка здоровья сервиса"""
|
||||
return {
|
||||
"status": "healthy",
|
||||
"service": "document-service",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""Корневой эндпоинт"""
|
||||
return {
|
||||
"message": "ERP Document Service",
|
||||
"docs": "/docs",
|
||||
"health": "/health"
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(
|
||||
"app.main:app",
|
||||
host=settings.HOST,
|
||||
port=settings.PORT,
|
||||
reload=settings.DEBUG,
|
||||
log_level=settings.LOG_LEVEL.lower()
|
||||
)
|
||||
86
doc-service/app/models/document.py
Normal file
86
doc-service/app/models/document.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from typing import Optional, Dict, Any, List
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
|
||||
class DocumentType(str, Enum):
|
||||
"""Типы документов"""
|
||||
PDF = "pdf"
|
||||
EXCEL = "excel"
|
||||
WORD = "word"
|
||||
QR_CODE = "qr_code"
|
||||
|
||||
class DocumentStatus(str, Enum):
|
||||
"""Статусы документов"""
|
||||
PENDING = "pending"
|
||||
PROCESSING = "processing"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
|
||||
class QRCodeRequest(BaseModel):
|
||||
"""Запрос на генерацию QR-кода"""
|
||||
location_id: str = Field(..., description="ID места хранения")
|
||||
location_address: str = Field(..., description="Адрес места")
|
||||
organization_id: str = Field(..., description="ID организации")
|
||||
size: Optional[int] = Field(10, description="Размер QR-кода")
|
||||
border: Optional[int] = Field(2, description="Размер границы")
|
||||
|
||||
class QRCodeResponse(BaseModel):
|
||||
"""Ответ с QR-кодом"""
|
||||
document_id: str = Field(..., description="ID документа")
|
||||
qr_code_url: str = Field(..., description="URL для скачивания QR-кода")
|
||||
qr_code_data: str = Field(..., description="Данные QR-кода")
|
||||
expires_at: datetime = Field(..., description="Время истечения")
|
||||
|
||||
class ReportRequest(BaseModel):
|
||||
"""Запрос на генерацию отчета"""
|
||||
report_type: str = Field(..., description="Тип отчета")
|
||||
organization_id: str = Field(..., description="ID организации")
|
||||
filters: Optional[Dict[str, Any]] = Field({}, description="Фильтры для отчета")
|
||||
format: DocumentType = Field(DocumentType.PDF, description="Формат отчета")
|
||||
|
||||
class ReportResponse(BaseModel):
|
||||
"""Ответ с отчетом"""
|
||||
document_id: str = Field(..., description="ID документа")
|
||||
download_url: str = Field(..., description="URL для скачивания")
|
||||
file_size: int = Field(..., description="Размер файла в байтах")
|
||||
expires_at: datetime = Field(..., description="Время истечения")
|
||||
|
||||
class DocumentStatusResponse(BaseModel):
|
||||
"""Ответ со статусом документа"""
|
||||
document_id: str = Field(..., description="ID документа")
|
||||
status: DocumentStatus = Field(..., description="Статус документа")
|
||||
progress: Optional[int] = Field(None, description="Прогресс в процентах")
|
||||
error_message: Optional[str] = Field(None, description="Сообщение об ошибке")
|
||||
created_at: datetime = Field(..., description="Время создания")
|
||||
updated_at: datetime = Field(..., description="Время обновления")
|
||||
|
||||
class GeneratePDFRequest(BaseModel):
|
||||
"""Запрос на генерацию PDF"""
|
||||
template_name: str = Field(..., description="Название шаблона")
|
||||
data: Dict[str, Any] = Field(..., description="Данные для шаблона")
|
||||
filename: Optional[str] = Field(None, description="Имя файла")
|
||||
|
||||
class GenerateExcelRequest(BaseModel):
|
||||
"""Запрос на генерацию Excel"""
|
||||
data: List[Dict[str, Any]] = Field(..., description="Данные для таблицы")
|
||||
sheet_name: str = Field("Sheet1", description="Название листа")
|
||||
filename: Optional[str] = Field(None, description="Имя файла")
|
||||
|
||||
class TemplateInfo(BaseModel):
|
||||
"""Информация о шаблоне"""
|
||||
name: str = Field(..., description="Название шаблона")
|
||||
description: str = Field(..., description="Описание шаблона")
|
||||
variables: List[str] = Field(..., description="Переменные шаблона")
|
||||
created_at: datetime = Field(..., description="Время создания")
|
||||
updated_at: datetime = Field(..., description="Время обновления")
|
||||
|
||||
class DocumentInfo(BaseModel):
|
||||
"""Информация о документе"""
|
||||
id: str = Field(..., description="ID документа")
|
||||
type: DocumentType = Field(..., description="Тип документа")
|
||||
status: DocumentStatus = Field(..., description="Статус документа")
|
||||
filename: str = Field(..., description="Имя файла")
|
||||
file_size: int = Field(..., description="Размер файла")
|
||||
created_at: datetime = Field(..., description="Время создания")
|
||||
expires_at: datetime = Field(..., description="Время истечения")
|
||||
43
doc-service/requirements.txt
Normal file
43
doc-service/requirements.txt
Normal file
@@ -0,0 +1,43 @@
|
||||
# Web framework
|
||||
fastapi==0.104.1
|
||||
uvicorn==0.24.0
|
||||
|
||||
# PDF generation
|
||||
reportlab==4.0.4
|
||||
weasyprint==60.1
|
||||
|
||||
# Office documents
|
||||
python-docx==1.1.0
|
||||
openpyxl==3.1.2
|
||||
|
||||
# Templates
|
||||
jinja2==3.1.2
|
||||
|
||||
# Redis client
|
||||
redis==5.0.1
|
||||
|
||||
# gRPC
|
||||
grpcio==1.59.0
|
||||
protobuf==4.24.4
|
||||
|
||||
# Data validation
|
||||
pydantic==2.4.2
|
||||
|
||||
# Environment variables
|
||||
python-dotenv==1.0.0
|
||||
|
||||
# Logging
|
||||
structlog==23.2.0
|
||||
|
||||
# QR code generation
|
||||
qrcode==7.4.2
|
||||
Pillow==10.0.1
|
||||
|
||||
# HTTP client
|
||||
httpx==0.25.1
|
||||
|
||||
# CORS
|
||||
fastapi-cors==0.0.6
|
||||
|
||||
# Monitoring
|
||||
prometheus-client==0.19.0
|
||||
179
docker/docker-compose.yml
Normal file
179
docker/docker-compose.yml
Normal file
@@ -0,0 +1,179 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# Core Service (Go)
|
||||
core-service:
|
||||
build:
|
||||
context: ./core-service
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- SERVER_HOST=0.0.0.0
|
||||
- SERVER_PORT=8080
|
||||
- DB_HOST=postgres
|
||||
- DB_PORT=5432
|
||||
- DB_USER=erp_user
|
||||
- DB_PASSWORD=erp_pass
|
||||
- DB_NAME=erp_mvp
|
||||
- DB_SSLMODE=disable
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_PASSWORD=
|
||||
- REDIS_DB=0
|
||||
- JWT_SECRET=your-super-secret-jwt-key-change-in-production
|
||||
- JWT_EXPIRATION=24
|
||||
- DOC_SERVICE_URL=http://doc-service:8000
|
||||
- LOG_LEVEL=info
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
networks:
|
||||
- erp-network
|
||||
restart: unless-stopped
|
||||
|
||||
# Document Service (Python)
|
||||
doc-service:
|
||||
build:
|
||||
context: ./doc-service
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- HOST=0.0.0.0
|
||||
- PORT=8000
|
||||
- DEBUG=false
|
||||
- LOG_LEVEL=INFO
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_PASSWORD=
|
||||
- REDIS_DB=1
|
||||
- CORE_SERVICE_URL=http://core-service:8080
|
||||
- DOCUMENTS_CACHE_TTL=86400
|
||||
- MAX_DOCUMENT_SIZE=10485760
|
||||
- TEMPLATES_DIR=/app/templates
|
||||
- OUTPUT_DIR=/app/output
|
||||
- QR_CODE_SIZE=10
|
||||
- QR_CODE_BORDER=2
|
||||
volumes:
|
||||
- doc_templates:/app/templates
|
||||
- doc_output:/app/output
|
||||
depends_on:
|
||||
- redis
|
||||
networks:
|
||||
- erp-network
|
||||
restart: unless-stopped
|
||||
|
||||
# Frontend (Angular)
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "3000:80"
|
||||
environment:
|
||||
- API_URL=http://localhost:8080
|
||||
- DOC_SERVICE_URL=http://localhost:8000
|
||||
depends_on:
|
||||
- core-service
|
||||
networks:
|
||||
- erp-network
|
||||
restart: unless-stopped
|
||||
|
||||
# Database (PostgreSQL)
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
- POSTGRES_DB=erp_mvp
|
||||
- POSTGRES_USER=erp_user
|
||||
- POSTGRES_PASSWORD=erp_pass
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./docker/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
networks:
|
||||
- erp-network
|
||||
restart: unless-stopped
|
||||
|
||||
# Cache (Redis)
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
command: redis-server --appendonly yes
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
networks:
|
||||
- erp-network
|
||||
restart: unless-stopped
|
||||
|
||||
# API Gateway (Traefik) - опционально
|
||||
traefik:
|
||||
image: traefik:v2.10
|
||||
command:
|
||||
- --api.insecure=true
|
||||
- --providers.docker=true
|
||||
- --providers.docker.exposedbydefault=false
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "8081:8080"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- erp-network
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- gateway
|
||||
|
||||
# Monitoring (Prometheus)
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
ports:
|
||||
- "9090:9090"
|
||||
volumes:
|
||||
- ./docker/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- prometheus_data:/prometheus
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
- '--web.console.libraries=/etc/prometheus/console_libraries'
|
||||
- '--web.console.templates=/etc/prometheus/consoles'
|
||||
- '--storage.tsdb.retention.time=200h'
|
||||
- '--web.enable-lifecycle'
|
||||
networks:
|
||||
- erp-network
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- monitoring
|
||||
|
||||
# Monitoring (Grafana)
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
ports:
|
||||
- "3001:3000"
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
- ./docker/grafana/provisioning:/etc/grafana/provisioning
|
||||
networks:
|
||||
- erp-network
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- monitoring
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
doc_templates:
|
||||
doc_output:
|
||||
prometheus_data:
|
||||
grafana_data:
|
||||
|
||||
networks:
|
||||
erp-network:
|
||||
driver: bridge
|
||||
124
docker/postgres/init.sql
Normal file
124
docker/postgres/init.sql
Normal file
@@ -0,0 +1,124 @@
|
||||
-- Инициализация базы данных ERP MVP
|
||||
|
||||
-- Создание расширений
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- Создание таблиц
|
||||
|
||||
-- Организации
|
||||
CREATE TABLE IF NOT EXISTS organizations (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(100),
|
||||
settings JSONB DEFAULT '{}',
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Пользователи
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
role VARCHAR(50) DEFAULT 'user',
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Места хранения
|
||||
CREATE TABLE IF NOT EXISTS storage_locations (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
parent_id UUID REFERENCES storage_locations(id) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
address VARCHAR(100) NOT NULL,
|
||||
type VARCHAR(50) NOT NULL,
|
||||
coordinates JSONB DEFAULT '{}',
|
||||
qr_code VARCHAR(255),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Товары
|
||||
CREATE TABLE IF NOT EXISTS items (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
category VARCHAR(100),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Размещение товаров
|
||||
CREATE TABLE IF NOT EXISTS item_placements (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
item_id UUID NOT NULL REFERENCES items(id) ON DELETE CASCADE,
|
||||
location_id UUID NOT NULL REFERENCES storage_locations(id) ON DELETE CASCADE,
|
||||
quantity INTEGER DEFAULT 1,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Операции
|
||||
CREATE TABLE IF NOT EXISTS operations (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
operation_type VARCHAR(50) NOT NULL,
|
||||
item_id UUID REFERENCES items(id) ON DELETE SET NULL,
|
||||
from_location_id UUID REFERENCES storage_locations(id) ON DELETE SET NULL,
|
||||
to_location_id UUID REFERENCES storage_locations(id) ON DELETE SET NULL,
|
||||
quantity INTEGER,
|
||||
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
metadata JSONB DEFAULT '{}',
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Создание индексов для производительности
|
||||
CREATE INDEX IF NOT EXISTS idx_users_organization_id ON users(organization_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_storage_locations_organization_id ON storage_locations(organization_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_storage_locations_parent_id ON storage_locations(parent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_storage_locations_type ON storage_locations(type);
|
||||
CREATE INDEX IF NOT EXISTS idx_items_organization_id ON items(organization_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_items_category ON items(category);
|
||||
CREATE INDEX IF NOT EXISTS idx_item_placements_organization_id ON item_placements(organization_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_item_placements_item_id ON item_placements(item_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_item_placements_location_id ON item_placements(location_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_operations_organization_id ON operations(organization_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_operations_created_at ON operations(created_at);
|
||||
|
||||
-- Создание триггеров для автоматического обновления updated_at
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_organizations_updated_at BEFORE UPDATE ON organizations
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_storage_locations_updated_at BEFORE UPDATE ON storage_locations
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_items_updated_at BEFORE UPDATE ON items
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_item_placements_updated_at BEFORE UPDATE ON item_placements
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- Создание тестовых данных (опционально)
|
||||
INSERT INTO organizations (id, name, type) VALUES
|
||||
('550e8400-e29b-41d4-a716-446655440000', 'Тестовая мастерская', 'workshop')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
INSERT INTO users (id, organization_id, email, password_hash, role) VALUES
|
||||
('550e8400-e29b-41d4-a716-446655440001', '550e8400-e29b-41d4-a716-446655440000', 'admin@test.com', '$2a$10$example', 'admin')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
45
frontend/Dockerfile
Normal file
45
frontend/Dockerfile
Normal file
@@ -0,0 +1,45 @@
|
||||
# Многоэтапная сборка для Angular приложения
|
||||
FROM node:18-alpine AS builder
|
||||
|
||||
# Установка рабочей директории
|
||||
WORKDIR /app
|
||||
|
||||
# Копирование package файлов
|
||||
COPY package*.json ./
|
||||
|
||||
# Установка зависимостей
|
||||
RUN npm ci --only=production
|
||||
|
||||
# Копирование исходного кода
|
||||
COPY . .
|
||||
|
||||
# Сборка приложения
|
||||
RUN npm run build:prod
|
||||
|
||||
# Финальный образ с nginx
|
||||
FROM nginx:alpine
|
||||
|
||||
# Копирование собранного приложения
|
||||
COPY --from=builder /app/dist/erp-mvp-frontend /usr/share/nginx/html
|
||||
|
||||
# Копирование конфигурации nginx
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
# Создание пользователя для безопасности
|
||||
RUN addgroup -g 1001 -S appgroup && \
|
||||
adduser -u 1001 -S appuser -G appgroup
|
||||
|
||||
# Смена владельца файлов
|
||||
RUN chown -R appuser:appgroup /usr/share/nginx/html && \
|
||||
chown -R appuser:appgroup /var/cache/nginx && \
|
||||
chown -R appuser:appgroup /var/log/nginx && \
|
||||
chown -R appuser:appgroup /etc/nginx/conf.d
|
||||
|
||||
# Переключение на непривилегированного пользователя
|
||||
USER appuser
|
||||
|
||||
# Экспорт порта
|
||||
EXPOSE 80
|
||||
|
||||
# Команда запуска
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
141
frontend/angular.json
Normal file
141
frontend/angular.json
Normal file
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"erp-mvp-frontend": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss",
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:class": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:pipe": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:resolver": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:service": {
|
||||
"skipTests": true
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/erp-mvp-frontend",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest"
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/indigo-pink.css",
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb",
|
||||
"maximumError": "10kb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all",
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "erp-mvp-frontend:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "erp-mvp-frontend:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "erp-mvp-frontend:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/indigo-pink.css",
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-eslint/builder:lint",
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.html"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"analytics": false
|
||||
}
|
||||
}
|
||||
91
frontend/nginx.conf
Normal file
91
frontend/nginx.conf
Normal file
@@ -0,0 +1,91 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Логирование
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
|
||||
# Основные настройки
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
|
||||
# Gzip сжатие
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_types
|
||||
text/plain
|
||||
text/css
|
||||
text/xml
|
||||
text/javascript
|
||||
application/json
|
||||
application/javascript
|
||||
application/xml+rss
|
||||
application/atom+xml
|
||||
image/svg+xml;
|
||||
|
||||
# Основной сервер
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Безопасность
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
|
||||
|
||||
# Кэширование статических файлов
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Angular routing - fallback на index.html
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# API проксирование (опционально)
|
||||
location /api/ {
|
||||
proxy_pass http://core-service:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Document Service проксирование
|
||||
location /doc-api/ {
|
||||
proxy_pass http://doc-service:8000/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Health check
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
frontend/package.json
Normal file
67
frontend/package.json
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"name": "erp-mvp-frontend",
|
||||
"version": "1.0.0",
|
||||
"description": "ERP для мастеров - Angular PWA Frontend",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"build:prod": "ng build --configuration production",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e",
|
||||
"pwa:build": "ng build --configuration production && ng add @angular/pwa"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.0.0",
|
||||
"@angular/common": "^17.0.0",
|
||||
"@angular/compiler": "^17.0.0",
|
||||
"@angular/core": "^17.0.0",
|
||||
"@angular/forms": "^17.0.0",
|
||||
"@angular/platform-browser": "^17.0.0",
|
||||
"@angular/platform-browser-dynamic": "^17.0.0",
|
||||
"@angular/router": "^17.0.0",
|
||||
"@angular/service-worker": "^17.0.0",
|
||||
"@angular/material": "^17.0.0",
|
||||
"@angular/cdk": "^17.0.0",
|
||||
"@zxing/ngx-scanner": "^3.0.0",
|
||||
"@ngrx/store": "^17.0.0",
|
||||
"@ngrx/effects": "^17.0.0",
|
||||
"@ngrx/entity": "^17.0.0",
|
||||
"@ngrx/store-devtools": "^17.0.0",
|
||||
"rxjs": "^7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.2",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"postcss": "^8.4.31"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^17.0.0",
|
||||
"@angular/cli": "^17.0.0",
|
||||
"@angular/compiler-cli": "^17.0.0",
|
||||
"@angular/language-service": "^17.0.0",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^18.0.0",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"typescript": "~5.2.2",
|
||||
"eslint": "^8.0.0",
|
||||
"@angular-eslint/eslint-plugin": "^17.0.0",
|
||||
"@angular-eslint/eslint-plugin-template": "^17.0.0",
|
||||
"@angular-eslint/template-parser": "^17.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=9.0.0"
|
||||
}
|
||||
}
|
||||
100
frontend/src/app/app.module.ts
Normal file
100
frontend/src/app/app.module.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
// Angular Material
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatChipsModule } from '@angular/material/chips';
|
||||
import { MatBadgeModule } from '@angular/material/badge';
|
||||
|
||||
// NgRx
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||
|
||||
// QR Scanner
|
||||
import { ZXingScannerModule } from '@zxing/ngx-scanner';
|
||||
|
||||
// Components
|
||||
import { AppComponent } from './app.component';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { HeaderComponent } from './components/header/header.component';
|
||||
import { QRScannerComponent } from './components/qr-scanner/qr-scanner.component';
|
||||
import { LocationDetailsComponent } from './components/location-details/location-details.component';
|
||||
import { ItemListComponent } from './components/item-list/item-list.component';
|
||||
import { SearchComponent } from './components/search/search.component';
|
||||
|
||||
// Services
|
||||
import { AuthInterceptor } from './services/auth.interceptor';
|
||||
|
||||
// Store
|
||||
import { appReducers } from './store/app.reducers';
|
||||
import { AppEffects } from './store/app.effects';
|
||||
|
||||
// Environment
|
||||
import { environment } from '../environments/environment';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HeaderComponent,
|
||||
QRScannerComponent,
|
||||
LocationDetailsComponent,
|
||||
ItemListComponent,
|
||||
SearchComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
HttpClientModule,
|
||||
ReactiveFormsModule,
|
||||
AppRoutingModule,
|
||||
|
||||
// Angular Material
|
||||
MatToolbarModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatCardModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatSnackBarModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatDialogModule,
|
||||
MatListModule,
|
||||
MatChipsModule,
|
||||
MatBadgeModule,
|
||||
|
||||
// QR Scanner
|
||||
ZXingScannerModule,
|
||||
|
||||
// NgRx
|
||||
StoreModule.forRoot(appReducers),
|
||||
EffectsModule.forRoot([AppEffects]),
|
||||
StoreDevtoolsModule.instrument({
|
||||
maxAge: 25,
|
||||
logOnly: environment.production
|
||||
})
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: AuthInterceptor,
|
||||
multi: true
|
||||
}
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
12
frontend/src/main.ts
Normal file
12
frontend/src/main.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { enableProdMode } from '@angular/core';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
||||
197
proto/core.proto
Normal file
197
proto/core.proto
Normal file
@@ -0,0 +1,197 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package erp.core;
|
||||
|
||||
option go_package = "erp-mvp/core-service/proto/core";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
// Сервис для работы с местами хранения
|
||||
service LocationService {
|
||||
rpc GetLocation(GetLocationRequest) returns (LocationResponse);
|
||||
rpc CreateLocation(CreateLocationRequest) returns (LocationResponse);
|
||||
rpc UpdateLocation(UpdateLocationRequest) returns (LocationResponse);
|
||||
rpc DeleteLocation(DeleteLocationRequest) returns (DeleteLocationResponse);
|
||||
rpc ListLocations(ListLocationsRequest) returns (ListLocationsResponse);
|
||||
}
|
||||
|
||||
// Сервис для работы с товарами
|
||||
service ItemService {
|
||||
rpc GetItem(GetItemRequest) returns (ItemResponse);
|
||||
rpc CreateItem(CreateItemRequest) returns (ItemResponse);
|
||||
rpc UpdateItem(UpdateItemRequest) returns (ItemResponse);
|
||||
rpc DeleteItem(DeleteItemRequest) returns (DeleteItemResponse);
|
||||
rpc ListItems(ListItemsRequest) returns (ListItemsResponse);
|
||||
}
|
||||
|
||||
// Сервис для операций
|
||||
service OperationService {
|
||||
rpc PlaceItem(PlaceItemRequest) returns (OperationResponse);
|
||||
rpc MoveItem(MoveItemRequest) returns (OperationResponse);
|
||||
rpc SearchItems(SearchItemsRequest) returns (SearchItemsResponse);
|
||||
}
|
||||
|
||||
// Запросы для мест хранения
|
||||
message GetLocationRequest {
|
||||
string location_id = 1;
|
||||
string organization_id = 2;
|
||||
}
|
||||
|
||||
message CreateLocationRequest {
|
||||
string organization_id = 1;
|
||||
string name = 2;
|
||||
string address = 3;
|
||||
string type = 4;
|
||||
optional string parent_id = 5;
|
||||
map<string, string> coordinates = 6;
|
||||
}
|
||||
|
||||
message UpdateLocationRequest {
|
||||
string location_id = 1;
|
||||
string organization_id = 2;
|
||||
optional string name = 3;
|
||||
optional string address = 4;
|
||||
optional string type = 5;
|
||||
optional string parent_id = 6;
|
||||
map<string, string> coordinates = 7;
|
||||
}
|
||||
|
||||
message DeleteLocationRequest {
|
||||
string location_id = 1;
|
||||
string organization_id = 2;
|
||||
}
|
||||
|
||||
message ListLocationsRequest {
|
||||
string organization_id = 1;
|
||||
optional string parent_id = 2;
|
||||
optional string type = 3;
|
||||
int32 page = 4;
|
||||
int32 page_size = 5;
|
||||
}
|
||||
|
||||
// Запросы для товаров
|
||||
message GetItemRequest {
|
||||
string item_id = 1;
|
||||
string organization_id = 2;
|
||||
}
|
||||
|
||||
message CreateItemRequest {
|
||||
string organization_id = 1;
|
||||
string name = 2;
|
||||
string description = 3;
|
||||
string category = 4;
|
||||
}
|
||||
|
||||
message UpdateItemRequest {
|
||||
string item_id = 1;
|
||||
string organization_id = 2;
|
||||
optional string name = 3;
|
||||
optional string description = 4;
|
||||
optional string category = 5;
|
||||
}
|
||||
|
||||
message DeleteItemRequest {
|
||||
string item_id = 1;
|
||||
string organization_id = 2;
|
||||
}
|
||||
|
||||
message ListItemsRequest {
|
||||
string organization_id = 1;
|
||||
optional string category = 2;
|
||||
optional string search_query = 3;
|
||||
int32 page = 4;
|
||||
int32 page_size = 5;
|
||||
}
|
||||
|
||||
// Запросы для операций
|
||||
message PlaceItemRequest {
|
||||
string organization_id = 1;
|
||||
string item_id = 2;
|
||||
string location_id = 3;
|
||||
int32 quantity = 4;
|
||||
}
|
||||
|
||||
message MoveItemRequest {
|
||||
string organization_id = 1;
|
||||
string item_id = 2;
|
||||
string from_location_id = 3;
|
||||
string to_location_id = 4;
|
||||
int32 quantity = 5;
|
||||
}
|
||||
|
||||
message SearchItemsRequest {
|
||||
string organization_id = 1;
|
||||
string query = 2;
|
||||
optional string category = 3;
|
||||
optional string location_id = 4;
|
||||
int32 page = 5;
|
||||
int32 page_size = 6;
|
||||
}
|
||||
|
||||
// Ответы
|
||||
message LocationResponse {
|
||||
string id = 1;
|
||||
string organization_id = 2;
|
||||
optional string parent_id = 3;
|
||||
string name = 4;
|
||||
string address = 5;
|
||||
string type = 6;
|
||||
map<string, string> coordinates = 7;
|
||||
string qr_code = 8;
|
||||
google.protobuf.Timestamp created_at = 9;
|
||||
google.protobuf.Timestamp updated_at = 10;
|
||||
}
|
||||
|
||||
message ListLocationsResponse {
|
||||
repeated LocationResponse locations = 1;
|
||||
int32 total_count = 2;
|
||||
int32 page = 3;
|
||||
int32 page_size = 4;
|
||||
}
|
||||
|
||||
message DeleteLocationResponse {
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message ItemResponse {
|
||||
string id = 1;
|
||||
string organization_id = 2;
|
||||
string name = 3;
|
||||
string description = 4;
|
||||
string category = 5;
|
||||
google.protobuf.Timestamp created_at = 6;
|
||||
google.protobuf.Timestamp updated_at = 7;
|
||||
}
|
||||
|
||||
message ListItemsResponse {
|
||||
repeated ItemResponse items = 1;
|
||||
int32 total_count = 2;
|
||||
int32 page = 3;
|
||||
int32 page_size = 4;
|
||||
}
|
||||
|
||||
message DeleteItemResponse {
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
message OperationResponse {
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
string operation_id = 3;
|
||||
google.protobuf.Timestamp created_at = 4;
|
||||
}
|
||||
|
||||
message SearchItemsResponse {
|
||||
repeated ItemWithLocation items = 1;
|
||||
int32 total_count = 2;
|
||||
int32 page = 3;
|
||||
int32 page_size = 4;
|
||||
}
|
||||
|
||||
message ItemWithLocation {
|
||||
ItemResponse item = 1;
|
||||
LocationResponse location = 2;
|
||||
int32 quantity = 3;
|
||||
}
|
||||
161
proto/document.proto
Normal file
161
proto/document.proto
Normal file
@@ -0,0 +1,161 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package erp.document;
|
||||
|
||||
option go_package = "erp-mvp/core-service/proto/document";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
// Сервис для генерации документов
|
||||
service DocumentService {
|
||||
rpc GenerateQRCode(QRCodeRequest) returns (QRCodeResponse);
|
||||
rpc GenerateReport(ReportRequest) returns (ReportResponse);
|
||||
rpc GetDocumentStatus(StatusRequest) returns (StatusResponse);
|
||||
rpc GetDocumentDownload(DownloadRequest) returns (DownloadResponse);
|
||||
}
|
||||
|
||||
// Сервис для работы с шаблонами
|
||||
service TemplateService {
|
||||
rpc GetTemplate(TemplateRequest) returns (TemplateResponse);
|
||||
rpc ListTemplates(ListTemplatesRequest) returns (ListTemplatesResponse);
|
||||
rpc CreateTemplate(CreateTemplateRequest) returns (TemplateResponse);
|
||||
rpc UpdateTemplate(UpdateTemplateRequest) returns (TemplateResponse);
|
||||
rpc DeleteTemplate(DeleteTemplateRequest) returns (DeleteTemplateResponse);
|
||||
}
|
||||
|
||||
// Запросы для QR-кодов
|
||||
message QRCodeRequest {
|
||||
string location_id = 1;
|
||||
string location_address = 2;
|
||||
string organization_id = 3;
|
||||
int32 size = 4;
|
||||
int32 border = 5;
|
||||
}
|
||||
|
||||
message QRCodeResponse {
|
||||
string document_id = 1;
|
||||
string qr_code_url = 2;
|
||||
string qr_code_data = 3;
|
||||
google.protobuf.Timestamp expires_at = 4;
|
||||
}
|
||||
|
||||
// Запросы для отчетов
|
||||
message ReportRequest {
|
||||
string report_type = 1;
|
||||
string organization_id = 2;
|
||||
map<string, string> filters = 3;
|
||||
DocumentFormat format = 4;
|
||||
}
|
||||
|
||||
message ReportResponse {
|
||||
string document_id = 1;
|
||||
string download_url = 2;
|
||||
int64 file_size = 3;
|
||||
google.protobuf.Timestamp expires_at = 4;
|
||||
}
|
||||
|
||||
// Запросы для статуса документа
|
||||
message StatusRequest {
|
||||
string document_id = 1;
|
||||
}
|
||||
|
||||
message StatusResponse {
|
||||
string document_id = 1;
|
||||
DocumentStatus status = 2;
|
||||
int32 progress = 3;
|
||||
string error_message = 4;
|
||||
google.protobuf.Timestamp created_at = 5;
|
||||
google.protobuf.Timestamp updated_at = 6;
|
||||
}
|
||||
|
||||
// Запросы для скачивания
|
||||
message DownloadRequest {
|
||||
string document_id = 1;
|
||||
}
|
||||
|
||||
message DownloadResponse {
|
||||
string document_id = 1;
|
||||
string download_url = 2;
|
||||
int64 file_size = 3;
|
||||
string filename = 4;
|
||||
DocumentFormat format = 5;
|
||||
}
|
||||
|
||||
// Запросы для шаблонов
|
||||
message TemplateRequest {
|
||||
string template_name = 1;
|
||||
}
|
||||
|
||||
message TemplateResponse {
|
||||
string name = 1;
|
||||
string description = 2;
|
||||
repeated string variables = 3;
|
||||
string content = 4;
|
||||
google.protobuf.Timestamp created_at = 5;
|
||||
google.protobuf.Timestamp updated_at = 6;
|
||||
}
|
||||
|
||||
message ListTemplatesRequest {
|
||||
string category = 1;
|
||||
int32 page = 2;
|
||||
int32 page_size = 3;
|
||||
}
|
||||
|
||||
message ListTemplatesResponse {
|
||||
repeated TemplateInfo templates = 1;
|
||||
int32 total_count = 2;
|
||||
int32 page = 3;
|
||||
int32 page_size = 4;
|
||||
}
|
||||
|
||||
message CreateTemplateRequest {
|
||||
string name = 1;
|
||||
string description = 2;
|
||||
string content = 3;
|
||||
string category = 4;
|
||||
repeated string variables = 5;
|
||||
}
|
||||
|
||||
message UpdateTemplateRequest {
|
||||
string name = 1;
|
||||
optional string description = 2;
|
||||
optional string content = 3;
|
||||
optional string category = 4;
|
||||
repeated string variables = 5;
|
||||
}
|
||||
|
||||
message DeleteTemplateRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message DeleteTemplateResponse {
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
// Информация о шаблоне
|
||||
message TemplateInfo {
|
||||
string name = 1;
|
||||
string description = 2;
|
||||
string category = 3;
|
||||
repeated string variables = 4;
|
||||
google.protobuf.Timestamp created_at = 5;
|
||||
google.protobuf.Timestamp updated_at = 6;
|
||||
}
|
||||
|
||||
// Перечисления
|
||||
enum DocumentFormat {
|
||||
DOCUMENT_FORMAT_UNSPECIFIED = 0;
|
||||
DOCUMENT_FORMAT_PDF = 1;
|
||||
DOCUMENT_FORMAT_EXCEL = 2;
|
||||
DOCUMENT_FORMAT_WORD = 3;
|
||||
DOCUMENT_FORMAT_QR_CODE = 4;
|
||||
}
|
||||
|
||||
enum DocumentStatus {
|
||||
DOCUMENT_STATUS_UNSPECIFIED = 0;
|
||||
DOCUMENT_STATUS_PENDING = 1;
|
||||
DOCUMENT_STATUS_PROCESSING = 2;
|
||||
DOCUMENT_STATUS_COMPLETED = 3;
|
||||
DOCUMENT_STATUS_FAILED = 4;
|
||||
}
|
||||
Reference in New Issue
Block a user