- Создан скрипт scripts/coverage.sh для автоматизированного анализа - Добавлена документация COVERAGE.md с детальным анализом - Текущее покрытие: 9.6% (низкое, нуждается в улучшении) Инструменты: - go test ./... -cover - быстрый анализ - go tool cover -func=coverage.out - детальная статистика - ./scripts/coverage.sh --html - HTML отчет - ./scripts/coverage.sh --threshold=80 - с порогом покрытия Рекомендации по улучшению: 1. Handlers (0% покрытия) - приоритет 1 2. Service Layer (0% покрытия) - приоритет 1 3. Middleware (0% покрытия) - приоритет 2 4. Repository (24.5% покрытия) - приоритет 2 5. Config & Database (0% покрытия) - приоритет 3 Цель: довести покрытие до 70%+
2999 lines
121 KiB
HTML
2999 lines
121 KiB
HTML
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||
<title>cmd: Go Coverage Report</title>
|
||
<style>
|
||
body {
|
||
background: black;
|
||
color: rgb(80, 80, 80);
|
||
}
|
||
body, pre, #legend span {
|
||
font-family: Menlo, monospace;
|
||
font-weight: bold;
|
||
}
|
||
#topbar {
|
||
background: black;
|
||
position: fixed;
|
||
top: 0; left: 0; right: 0;
|
||
height: 42px;
|
||
border-bottom: 1px solid rgb(80, 80, 80);
|
||
}
|
||
#content {
|
||
margin-top: 50px;
|
||
}
|
||
#nav, #legend {
|
||
float: left;
|
||
margin-left: 10px;
|
||
}
|
||
#legend {
|
||
margin-top: 12px;
|
||
}
|
||
#nav {
|
||
margin-top: 10px;
|
||
}
|
||
#legend span {
|
||
margin: 0 5px;
|
||
}
|
||
.cov0 { color: rgb(192, 0, 0) }
|
||
.cov1 { color: rgb(128, 128, 128) }
|
||
.cov2 { color: rgb(116, 140, 131) }
|
||
.cov3 { color: rgb(104, 152, 134) }
|
||
.cov4 { color: rgb(92, 164, 137) }
|
||
.cov5 { color: rgb(80, 176, 140) }
|
||
.cov6 { color: rgb(68, 188, 143) }
|
||
.cov7 { color: rgb(56, 200, 146) }
|
||
.cov8 { color: rgb(44, 212, 149) }
|
||
.cov9 { color: rgb(32, 224, 152) }
|
||
.cov10 { color: rgb(20, 236, 155) }
|
||
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="topbar">
|
||
<div id="nav">
|
||
<select id="files">
|
||
|
||
<option value="file0">erp-mvp/core-service/cmd/main.go (0.0%)</option>
|
||
|
||
<option value="file1">erp-mvp/core-service/internal/api/handlers/auth.go (0.0%)</option>
|
||
|
||
<option value="file2">erp-mvp/core-service/internal/api/handlers/items.go (0.0%)</option>
|
||
|
||
<option value="file3">erp-mvp/core-service/internal/api/handlers/locations.go (0.0%)</option>
|
||
|
||
<option value="file4">erp-mvp/core-service/internal/api/handlers/operations.go (0.0%)</option>
|
||
|
||
<option value="file5">erp-mvp/core-service/internal/api/middleware/auth.go (0.0%)</option>
|
||
|
||
<option value="file6">erp-mvp/core-service/internal/api/server.go (0.0%)</option>
|
||
|
||
<option value="file7">erp-mvp/core-service/internal/auth/jwt.go (84.6%)</option>
|
||
|
||
<option value="file8">erp-mvp/core-service/internal/auth/password.go (100.0%)</option>
|
||
|
||
<option value="file9">erp-mvp/core-service/internal/config/config.go (0.0%)</option>
|
||
|
||
<option value="file10">erp-mvp/core-service/internal/database/connection.go (0.0%)</option>
|
||
|
||
<option value="file11">erp-mvp/core-service/internal/logger/logger.go (0.0%)</option>
|
||
|
||
<option value="file12">erp-mvp/core-service/internal/repository/items.go (6.6%)</option>
|
||
|
||
<option value="file13">erp-mvp/core-service/internal/repository/locations.go (20.2%)</option>
|
||
|
||
<option value="file14">erp-mvp/core-service/internal/repository/operations.go (27.9%)</option>
|
||
|
||
<option value="file15">erp-mvp/core-service/internal/repository/organizations.go (50.0%)</option>
|
||
|
||
<option value="file16">erp-mvp/core-service/internal/repository/users.go (45.5%)</option>
|
||
|
||
<option value="file17">erp-mvp/core-service/internal/service/auth_service.go (0.0%)</option>
|
||
|
||
<option value="file18">erp-mvp/core-service/internal/service/item_service.go (0.0%)</option>
|
||
|
||
<option value="file19">erp-mvp/core-service/internal/service/location_service.go (0.0%)</option>
|
||
|
||
<option value="file20">erp-mvp/core-service/internal/service/operations_service.go (0.0%)</option>
|
||
|
||
</select>
|
||
</div>
|
||
<div id="legend">
|
||
<span>not tracked</span>
|
||
|
||
<span class="cov0">not covered</span>
|
||
<span class="cov8">covered</span>
|
||
|
||
</div>
|
||
</div>
|
||
<div id="content">
|
||
|
||
<pre class="file" id="file0" style="display: none">package main
|
||
|
||
import (
|
||
"context"
|
||
"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/logger"
|
||
)
|
||
|
||
func main() <span class="cov0" title="0">{
|
||
// Инициализация логгера
|
||
logger := logger.New()
|
||
|
||
// Загрузка конфигурации
|
||
cfg, err := config.Load()
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
logger.Fatal("Failed to load config", err)
|
||
}</span>
|
||
|
||
// Подключение к базе данных
|
||
<span class="cov0" title="0">db, err := database.Connect(cfg.Database)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
logger.Fatal("Failed to connect to database", err)
|
||
}</span>
|
||
<span class="cov0" title="0">defer db.Close()
|
||
|
||
// Создание API сервера
|
||
server := api.NewServer(cfg, db, logger)
|
||
|
||
// Запуск HTTP сервера
|
||
go func() </span><span class="cov0" title="0">{
|
||
logger.Info("Starting HTTP server on", cfg.Server.Port)
|
||
if err := server.Start(); err != nil && err != http.ErrServerClosed </span><span class="cov0" title="0">{
|
||
logger.Fatal("Failed to start server", err)
|
||
}</span>
|
||
}()
|
||
|
||
// Graceful shutdown
|
||
<span class="cov0" title="0">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 </span><span class="cov0" title="0">{
|
||
logger.Fatal("Server forced to shutdown", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">logger.Info("Server exited")</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file1" style="display: none">package handlers
|
||
|
||
import (
|
||
"net/http"
|
||
|
||
"erp-mvp/core-service/internal/models"
|
||
"erp-mvp/core-service/internal/service"
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/go-playground/validator/v10"
|
||
)
|
||
|
||
type AuthHandler struct {
|
||
authService service.AuthService
|
||
validate *validator.Validate
|
||
}
|
||
|
||
func NewAuthHandler(authService service.AuthService) *AuthHandler <span class="cov0" title="0">{
|
||
return &AuthHandler{
|
||
authService: authService,
|
||
validate: validator.New(),
|
||
}
|
||
}</span>
|
||
|
||
// Register регистрация новой организации и пользователя
|
||
func (h *AuthHandler) Register(c *gin.Context) <span class="cov0" title="0">{
|
||
var req models.RegisterRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
|
||
return
|
||
}</span>
|
||
|
||
// Валидируем запрос
|
||
<span class="cov0" title="0">if err := h.validate.Struct(req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Validation failed", "details": err.Error()})
|
||
return
|
||
}</span>
|
||
|
||
// Выполняем регистрацию
|
||
<span class="cov0" title="0">response, err := h.authService.Register(c.Request.Context(), &req)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
if validationErr, ok := err.(*service.ValidationError); ok </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": validationErr.Message})
|
||
return
|
||
}</span>
|
||
<span class="cov0" title="0">c.JSON(http.StatusInternalServerError, gin.H{"error": "Registration failed"})
|
||
return</span>
|
||
}
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusCreated, response)</span>
|
||
}
|
||
|
||
// Login вход в систему
|
||
func (h *AuthHandler) Login(c *gin.Context) <span class="cov0" title="0">{
|
||
var req models.LoginRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
|
||
return
|
||
}</span>
|
||
|
||
// Валидируем запрос
|
||
<span class="cov0" title="0">if err := h.validate.Struct(req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Validation failed", "details": err.Error()})
|
||
return
|
||
}</span>
|
||
|
||
// Выполняем вход
|
||
<span class="cov0" title="0">response, err := h.authService.Login(c.Request.Context(), &req)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
if validationErr, ok := err.(*service.ValidationError); ok </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": validationErr.Message})
|
||
return
|
||
}</span>
|
||
<span class="cov0" title="0">c.JSON(http.StatusInternalServerError, gin.H{"error": "Login failed"})
|
||
return</span>
|
||
}
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusOK, response)</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file2" style="display: none">package handlers
|
||
|
||
import (
|
||
"net/http"
|
||
|
||
"erp-mvp/core-service/internal/api/middleware"
|
||
"erp-mvp/core-service/internal/models"
|
||
"erp-mvp/core-service/internal/service"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/go-playground/validator/v10"
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
type ItemHandler struct {
|
||
itemService service.ItemService
|
||
validate *validator.Validate
|
||
}
|
||
|
||
func NewItemHandler(itemService service.ItemService) *ItemHandler <span class="cov0" title="0">{
|
||
return &ItemHandler{
|
||
itemService: itemService,
|
||
validate: validator.New(),
|
||
}
|
||
}</span>
|
||
|
||
// GetItems получает все товары организации
|
||
func (h *ItemHandler) GetItems(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">items, err := h.itemService.GetItems(c.Request.Context(), claims.OrganizationID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get items"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusOK, items)</span>
|
||
}
|
||
|
||
// CreateItem создает новый товар
|
||
func (h *ItemHandler) CreateItem(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">var req models.CreateItemRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := h.validate.Struct(req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Validation failed", "details": err.Error()})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">item, err := h.itemService.CreateItem(c.Request.Context(), claims.OrganizationID, &req)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create item"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusCreated, item)</span>
|
||
}
|
||
|
||
// GetItem получает товар по ID
|
||
func (h *ItemHandler) GetItem(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">idStr := c.Param("id")
|
||
id, err := uuid.Parse(idStr)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid item ID"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">item, err := h.itemService.GetItem(c.Request.Context(), id, claims.OrganizationID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Item not found"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusOK, item)</span>
|
||
}
|
||
|
||
// UpdateItem обновляет товар
|
||
func (h *ItemHandler) UpdateItem(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">idStr := c.Param("id")
|
||
id, err := uuid.Parse(idStr)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid item ID"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">var req models.CreateItemRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := h.validate.Struct(req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Validation failed", "details": err.Error()})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">item, err := h.itemService.UpdateItem(c.Request.Context(), id, claims.OrganizationID, &req)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Item not found"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusOK, item)</span>
|
||
}
|
||
|
||
// DeleteItem удаляет товар
|
||
func (h *ItemHandler) DeleteItem(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">idStr := c.Param("id")
|
||
id, err := uuid.Parse(idStr)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid item ID"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := h.itemService.DeleteItem(c.Request.Context(), id, claims.OrganizationID); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Item not found"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusNoContent, nil)</span>
|
||
}
|
||
|
||
// SearchItems ищет товары
|
||
func (h *ItemHandler) SearchItems(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">query := c.Query("q")
|
||
category := c.Query("category")
|
||
|
||
items, err := h.itemService.SearchItems(c.Request.Context(), claims.OrganizationID, query, category)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to search items"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusOK, items)</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file3" style="display: none">package handlers
|
||
|
||
import (
|
||
"net/http"
|
||
|
||
"erp-mvp/core-service/internal/api/middleware"
|
||
"erp-mvp/core-service/internal/models"
|
||
"erp-mvp/core-service/internal/service"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/go-playground/validator/v10"
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
type LocationHandler struct {
|
||
locationService service.LocationService
|
||
validate *validator.Validate
|
||
}
|
||
|
||
func NewLocationHandler(locationService service.LocationService) *LocationHandler <span class="cov0" title="0">{
|
||
return &LocationHandler{
|
||
locationService: locationService,
|
||
validate: validator.New(),
|
||
}
|
||
}</span>
|
||
|
||
// GetLocations получает все места хранения организации
|
||
func (h *LocationHandler) GetLocations(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">locations, err := h.locationService.GetLocations(c.Request.Context(), claims.OrganizationID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get locations"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusOK, locations)</span>
|
||
}
|
||
|
||
// CreateLocation создает новое место хранения
|
||
func (h *LocationHandler) CreateLocation(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">var req models.CreateLocationRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := h.validate.Struct(req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Validation failed", "details": err.Error()})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">location, err := h.locationService.CreateLocation(c.Request.Context(), claims.OrganizationID, &req)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create location"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusCreated, location)</span>
|
||
}
|
||
|
||
// GetLocation получает место хранения по ID
|
||
func (h *LocationHandler) GetLocation(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">idStr := c.Param("id")
|
||
id, err := uuid.Parse(idStr)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid location ID"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">location, err := h.locationService.GetLocation(c.Request.Context(), id, claims.OrganizationID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Location not found"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusOK, location)</span>
|
||
}
|
||
|
||
// UpdateLocation обновляет место хранения
|
||
func (h *LocationHandler) UpdateLocation(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">idStr := c.Param("id")
|
||
id, err := uuid.Parse(idStr)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid location ID"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">var req models.CreateLocationRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := h.validate.Struct(req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Validation failed", "details": err.Error()})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">location, err := h.locationService.UpdateLocation(c.Request.Context(), id, claims.OrganizationID, &req)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Location not found"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusOK, location)</span>
|
||
}
|
||
|
||
// DeleteLocation удаляет место хранения
|
||
func (h *LocationHandler) DeleteLocation(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">idStr := c.Param("id")
|
||
id, err := uuid.Parse(idStr)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid location ID"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := h.locationService.DeleteLocation(c.Request.Context(), id, claims.OrganizationID); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Location not found"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusNoContent, nil)</span>
|
||
}
|
||
|
||
// GetChildren получает дочерние места хранения
|
||
func (h *LocationHandler) GetChildren(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">parentIDStr := c.Param("id")
|
||
parentID, err := uuid.Parse(parentIDStr)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid parent location ID"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">children, err := h.locationService.GetChildren(c.Request.Context(), parentID, claims.OrganizationID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Parent location not found"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusOK, children)</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file4" style="display: none">package handlers
|
||
|
||
import (
|
||
"net/http"
|
||
|
||
"erp-mvp/core-service/internal/api/middleware"
|
||
"erp-mvp/core-service/internal/models"
|
||
"erp-mvp/core-service/internal/service"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/go-playground/validator/v10"
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
type OperationsHandler struct {
|
||
operationsService service.OperationsService
|
||
validate *validator.Validate
|
||
}
|
||
|
||
func NewOperationsHandler(operationsService service.OperationsService) *OperationsHandler <span class="cov0" title="0">{
|
||
return &OperationsHandler{
|
||
operationsService: operationsService,
|
||
validate: validator.New(),
|
||
}
|
||
}</span>
|
||
|
||
// PlaceItem размещает товар в месте хранения
|
||
func (h *OperationsHandler) PlaceItem(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">var req models.PlaceItemRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := h.validate.Struct(req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Validation failed", "details": err.Error()})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">placement, err := h.operationsService.PlaceItem(c.Request.Context(), claims.OrganizationID, &req)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to place item"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusCreated, placement)</span>
|
||
}
|
||
|
||
// MoveItem перемещает товар в другое место хранения
|
||
func (h *OperationsHandler) MoveItem(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">placementIDStr := c.Param("id")
|
||
placementID, err := uuid.Parse(placementIDStr)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid placement ID"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">var req struct {
|
||
NewLocationID uuid.UUID `json:"new_location_id" validate:"required"`
|
||
}
|
||
if err := c.ShouldBindJSON(&req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := h.validate.Struct(req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Validation failed", "details": err.Error()})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := h.operationsService.MoveItem(c.Request.Context(), placementID, req.NewLocationID, claims.OrganizationID); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Placement not found"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusOK, gin.H{"message": "Item moved successfully"})</span>
|
||
}
|
||
|
||
// GetItemPlacements получает все размещения товара
|
||
func (h *OperationsHandler) GetItemPlacements(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">itemIDStr := c.Param("item_id")
|
||
itemID, err := uuid.Parse(itemIDStr)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid item ID"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">placements, err := h.operationsService.GetItemPlacements(c.Request.Context(), itemID, claims.OrganizationID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Item not found"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusOK, placements)</span>
|
||
}
|
||
|
||
// GetLocationPlacements получает все товары в месте хранения
|
||
func (h *OperationsHandler) GetLocationPlacements(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">locationIDStr := c.Param("location_id")
|
||
locationID, err := uuid.Parse(locationIDStr)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid location ID"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">placements, err := h.operationsService.GetLocationPlacements(c.Request.Context(), locationID, claims.OrganizationID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Location not found"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusOK, placements)</span>
|
||
}
|
||
|
||
// UpdateQuantity обновляет количество товара в размещении
|
||
func (h *OperationsHandler) UpdateQuantity(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">placementIDStr := c.Param("id")
|
||
placementID, err := uuid.Parse(placementIDStr)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid placement ID"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">var req struct {
|
||
Quantity int `json:"quantity" validate:"required,min=1"`
|
||
}
|
||
if err := c.ShouldBindJSON(&req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := h.validate.Struct(req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Validation failed", "details": err.Error()})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := h.operationsService.UpdateQuantity(c.Request.Context(), placementID, req.Quantity, claims.OrganizationID); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Placement not found"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusOK, gin.H{"message": "Quantity updated successfully"})</span>
|
||
}
|
||
|
||
// DeletePlacement удаляет размещение товара
|
||
func (h *OperationsHandler) DeletePlacement(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">placementIDStr := c.Param("id")
|
||
placementID, err := uuid.Parse(placementIDStr)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid placement ID"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := h.operationsService.DeletePlacement(c.Request.Context(), placementID, claims.OrganizationID); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotFound, gin.H{"error": "Placement not found"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusNoContent, nil)</span>
|
||
}
|
||
|
||
// Search выполняет поиск товаров с местами размещения
|
||
func (h *OperationsHandler) Search(c *gin.Context) <span class="cov0" title="0">{
|
||
claims := middleware.GetClaims(c)
|
||
if claims == nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">var req models.SearchRequest
|
||
if err := c.ShouldBindQuery(&req); err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid query parameters"})
|
||
return
|
||
}</span>
|
||
|
||
// Устанавливаем значения по умолчанию
|
||
<span class="cov0" title="0">if req.Page <= 0 </span><span class="cov0" title="0">{
|
||
req.Page = 1
|
||
}</span>
|
||
<span class="cov0" title="0">if req.PageSize <= 0 </span><span class="cov0" title="0">{
|
||
req.PageSize = 20
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">response, err := h.operationsService.Search(c.Request.Context(), claims.OrganizationID, &req)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to search"})
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">c.JSON(http.StatusOK, response)</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file5" style="display: none">package middleware
|
||
|
||
import (
|
||
"net/http"
|
||
"strings"
|
||
|
||
"erp-mvp/core-service/internal/auth"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
type AuthMiddleware struct {
|
||
jwtService *auth.JWTService
|
||
}
|
||
|
||
func NewAuthMiddleware(jwtService *auth.JWTService) *AuthMiddleware <span class="cov0" title="0">{
|
||
return &AuthMiddleware{
|
||
jwtService: jwtService,
|
||
}
|
||
}</span>
|
||
|
||
func (m *AuthMiddleware) AuthRequired() gin.HandlerFunc <span class="cov0" title="0">{
|
||
return func(c *gin.Context) </span><span class="cov0" title="0">{
|
||
// Получаем токен из заголовка Authorization
|
||
authHeader := c.GetHeader("Authorization")
|
||
if authHeader == "" </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"})
|
||
c.Abort()
|
||
return
|
||
}</span>
|
||
|
||
// Проверяем формат "Bearer <token>"
|
||
<span class="cov0" title="0">tokenParts := strings.Split(authHeader, " ")
|
||
if len(tokenParts) != 2 || tokenParts[0] != "Bearer" </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid authorization header format"})
|
||
c.Abort()
|
||
return
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">tokenString := tokenParts[1]
|
||
|
||
// Валидируем токен
|
||
claims, err := m.jwtService.ValidateToken(tokenString)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
|
||
c.Abort()
|
||
return
|
||
}</span>
|
||
|
||
// Сохраняем claims в контексте
|
||
<span class="cov0" title="0">c.Set("user_id", claims.UserID)
|
||
c.Set("organization_id", claims.OrganizationID)
|
||
c.Set("email", claims.Email)
|
||
c.Set("role", claims.Role)
|
||
|
||
c.Next()</span>
|
||
}
|
||
}
|
||
|
||
// GetClaims получает claims из контекста Gin
|
||
func GetClaims(c *gin.Context) *auth.Claims <span class="cov0" title="0">{
|
||
userID, exists := c.Get("user_id")
|
||
if !exists </span><span class="cov0" title="0">{
|
||
return nil
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">orgID, exists := c.Get("organization_id")
|
||
if !exists </span><span class="cov0" title="0">{
|
||
return nil
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">email, exists := c.Get("email")
|
||
if !exists </span><span class="cov0" title="0">{
|
||
return nil
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">role, exists := c.Get("role")
|
||
if !exists </span><span class="cov0" title="0">{
|
||
return nil
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return &auth.Claims{
|
||
UserID: userID.(uuid.UUID),
|
||
OrganizationID: orgID.(uuid.UUID),
|
||
Email: email.(string),
|
||
Role: role.(string),
|
||
}</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file6" style="display: none">package api
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"net/http"
|
||
|
||
"erp-mvp/core-service/internal/api/handlers"
|
||
"erp-mvp/core-service/internal/api/middleware"
|
||
"erp-mvp/core-service/internal/auth"
|
||
"erp-mvp/core-service/internal/config"
|
||
"erp-mvp/core-service/internal/logger"
|
||
"erp-mvp/core-service/internal/repository"
|
||
"erp-mvp/core-service/internal/service"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
type Server struct {
|
||
config *config.Config
|
||
db *sql.DB
|
||
logger logger.Logger
|
||
router *gin.Engine
|
||
|
||
// Services
|
||
authService service.AuthService
|
||
locationService service.LocationService
|
||
itemService service.ItemService
|
||
operationsService service.OperationsService
|
||
|
||
// Handlers
|
||
authHandler *handlers.AuthHandler
|
||
locationHandler *handlers.LocationHandler
|
||
itemHandler *handlers.ItemHandler
|
||
operationsHandler *handlers.OperationsHandler
|
||
|
||
// Middleware
|
||
authMiddleware *middleware.AuthMiddleware
|
||
}
|
||
|
||
func NewServer(cfg *config.Config, db *sql.DB, log logger.Logger) *Server <span class="cov0" title="0">{
|
||
// Инициализируем JWT сервис
|
||
jwtService := auth.NewJWTService(cfg.JWT.Secret, cfg.JWT.TTL)
|
||
|
||
// Инициализируем репозитории
|
||
orgRepo := repository.NewOrganizationRepository(db)
|
||
userRepo := repository.NewUserRepository(db)
|
||
locationRepo := repository.NewLocationRepository(db)
|
||
itemRepo := repository.NewItemRepository(db)
|
||
operationsRepo := repository.NewOperationsRepository(db)
|
||
|
||
// Инициализируем сервисы
|
||
authService := service.NewAuthService(orgRepo, userRepo, jwtService)
|
||
locationService := service.NewLocationService(locationRepo)
|
||
itemService := service.NewItemService(itemRepo)
|
||
operationsService := service.NewOperationsService(operationsRepo, itemRepo, locationRepo)
|
||
|
||
// Инициализируем handlers
|
||
authHandler := handlers.NewAuthHandler(authService)
|
||
locationHandler := handlers.NewLocationHandler(locationService)
|
||
itemHandler := handlers.NewItemHandler(itemService)
|
||
operationsHandler := handlers.NewOperationsHandler(operationsService)
|
||
|
||
// Инициализируем middleware
|
||
authMiddleware := middleware.NewAuthMiddleware(jwtService)
|
||
|
||
server := &Server{
|
||
config: cfg,
|
||
db: db,
|
||
logger: log,
|
||
router: gin.Default(),
|
||
authService: authService,
|
||
locationService: locationService,
|
||
itemService: itemService,
|
||
operationsService: operationsService,
|
||
authHandler: authHandler,
|
||
locationHandler: locationHandler,
|
||
itemHandler: itemHandler,
|
||
operationsHandler: operationsHandler,
|
||
authMiddleware: authMiddleware,
|
||
}
|
||
|
||
server.setupRoutes()
|
||
return server
|
||
}</span>
|
||
|
||
func (s *Server) setupRoutes() <span class="cov0" title="0">{
|
||
// Health check
|
||
s.router.GET("/health", s.healthCheck)
|
||
|
||
// API routes
|
||
api := s.router.Group("/api")
|
||
</span><span class="cov0" title="0">{
|
||
// Auth routes
|
||
auth := api.Group("/auth")
|
||
</span><span class="cov0" title="0">{
|
||
auth.POST("/register", s.authHandler.Register)
|
||
auth.POST("/login", s.authHandler.Login)
|
||
}</span>
|
||
|
||
// Protected routes
|
||
<span class="cov0" title="0">protected := api.Group("/")
|
||
protected.Use(s.authMiddleware.AuthRequired())
|
||
</span><span class="cov0" title="0">{
|
||
// Organizations
|
||
protected.GET("/organizations/:id", s.getOrganization)
|
||
protected.PUT("/organizations/:id", s.updateOrganization)
|
||
|
||
// Locations
|
||
protected.GET("/locations", s.locationHandler.GetLocations)
|
||
protected.POST("/locations", s.locationHandler.CreateLocation)
|
||
protected.GET("/locations/:id", s.locationHandler.GetLocation)
|
||
protected.PUT("/locations/:id", s.locationHandler.UpdateLocation)
|
||
protected.DELETE("/locations/:id", s.locationHandler.DeleteLocation)
|
||
protected.GET("/locations/:id/children", s.locationHandler.GetChildren)
|
||
|
||
// Items
|
||
protected.GET("/items", s.itemHandler.GetItems)
|
||
protected.POST("/items", s.itemHandler.CreateItem)
|
||
protected.GET("/items/:id", s.itemHandler.GetItem)
|
||
protected.PUT("/items/:id", s.itemHandler.UpdateItem)
|
||
protected.DELETE("/items/:id", s.itemHandler.DeleteItem)
|
||
protected.GET("/items/search", s.itemHandler.SearchItems)
|
||
|
||
// Operations
|
||
protected.POST("/operations/place-item", s.operationsHandler.PlaceItem)
|
||
protected.POST("/operations/move-item/:id", s.operationsHandler.MoveItem)
|
||
protected.GET("/operations/search", s.operationsHandler.Search)
|
||
protected.GET("/operations/items/:item_id/placements", s.operationsHandler.GetItemPlacements)
|
||
protected.GET("/operations/locations/:location_id/placements", s.operationsHandler.GetLocationPlacements)
|
||
protected.PUT("/operations/placements/:id/quantity", s.operationsHandler.UpdateQuantity)
|
||
protected.DELETE("/operations/placements/:id", s.operationsHandler.DeletePlacement)
|
||
|
||
// Templates
|
||
protected.GET("/templates", s.getTemplates)
|
||
protected.POST("/templates/:id/apply", s.applyTemplate)
|
||
}</span>
|
||
}
|
||
}
|
||
|
||
func (s *Server) healthCheck(c *gin.Context) <span class="cov0" title="0">{
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"status": "ok",
|
||
"service": "erp-mvp-core",
|
||
})
|
||
}</span>
|
||
|
||
// Placeholder handlers - will be implemented in next stages
|
||
func (s *Server) getOrganization(c *gin.Context) <span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
|
||
}</span>
|
||
|
||
func (s *Server) updateOrganization(c *gin.Context) <span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
|
||
}</span>
|
||
|
||
func (s *Server) getTemplates(c *gin.Context) <span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
|
||
}</span>
|
||
|
||
func (s *Server) applyTemplate(c *gin.Context) <span class="cov0" title="0">{
|
||
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
|
||
}</span>
|
||
|
||
func (s *Server) Start() error <span class="cov0" title="0">{
|
||
return s.router.Run(s.config.Server.Host + ":" + s.config.Server.Port)
|
||
}</span>
|
||
|
||
func (s *Server) Shutdown(ctx context.Context) error <span class="cov0" title="0">{
|
||
// Graceful shutdown logic will be added later
|
||
return nil
|
||
}</span>
|
||
</pre>
|
||
|
||
<pre class="file" id="file7" style="display: none">package auth
|
||
|
||
import (
|
||
"errors"
|
||
"time"
|
||
|
||
"github.com/golang-jwt/jwt/v5"
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
type Claims struct {
|
||
UserID uuid.UUID `json:"user_id"`
|
||
OrganizationID uuid.UUID `json:"organization_id"`
|
||
Email string `json:"email"`
|
||
Role string `json:"role"`
|
||
jwt.RegisteredClaims
|
||
}
|
||
|
||
type JWTService struct {
|
||
secret string
|
||
ttl time.Duration
|
||
}
|
||
|
||
func NewJWTService(secret string, ttl time.Duration) *JWTService <span class="cov8" title="1">{
|
||
return &JWTService{
|
||
secret: secret,
|
||
ttl: ttl,
|
||
}
|
||
}</span>
|
||
|
||
// GenerateToken создаёт JWT токен для пользователя
|
||
func (j *JWTService) GenerateToken(userID, organizationID uuid.UUID, email, role string) (string, error) <span class="cov8" title="1">{
|
||
claims := Claims{
|
||
UserID: userID,
|
||
OrganizationID: organizationID,
|
||
Email: email,
|
||
Role: role,
|
||
RegisteredClaims: jwt.RegisteredClaims{
|
||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(j.ttl)),
|
||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||
Issuer: "erp-mvp-core",
|
||
Subject: userID.String(),
|
||
},
|
||
}
|
||
|
||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||
return token.SignedString([]byte(j.secret))
|
||
}</span>
|
||
|
||
// ValidateToken валидирует JWT токен и возвращает claims
|
||
func (j *JWTService) ValidateToken(tokenString string) (*Claims, error) <span class="cov8" title="1">{
|
||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) </span><span class="cov8" title="1">{
|
||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok </span><span class="cov0" title="0">{
|
||
return nil, errors.New("unexpected signing method")
|
||
}</span>
|
||
<span class="cov8" title="1">return []byte(j.secret), nil</span>
|
||
})
|
||
|
||
<span class="cov8" title="1">if err != nil </span><span class="cov8" title="1">{
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov8" title="1">if claims, ok := token.Claims.(*Claims); ok && token.Valid </span><span class="cov8" title="1">{
|
||
return claims, nil
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return nil, errors.New("invalid token")</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file8" style="display: none">package auth
|
||
|
||
import (
|
||
"golang.org/x/crypto/bcrypt"
|
||
)
|
||
|
||
// HashPassword хеширует пароль с использованием bcrypt
|
||
func HashPassword(password string) (string, error) <span class="cov8" title="1">{
|
||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||
return string(bytes), err
|
||
}</span>
|
||
|
||
// CheckPassword проверяет пароль против хеша
|
||
func CheckPassword(password, hash string) bool <span class="cov8" title="1">{
|
||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||
return err == nil
|
||
}</span>
|
||
</pre>
|
||
|
||
<pre class="file" id="file9" style="display: none">package config
|
||
|
||
import (
|
||
"os"
|
||
"strconv"
|
||
"time"
|
||
|
||
"github.com/joho/godotenv"
|
||
)
|
||
|
||
type Config struct {
|
||
Server ServerConfig
|
||
Database DatabaseConfig
|
||
JWT JWTConfig
|
||
}
|
||
|
||
type ServerConfig struct {
|
||
Port string
|
||
Host string
|
||
}
|
||
|
||
type DatabaseConfig struct {
|
||
Host string
|
||
Port string
|
||
User string
|
||
Password string
|
||
DBName string
|
||
SSLMode string
|
||
}
|
||
|
||
type JWTConfig struct {
|
||
Secret string
|
||
TTL time.Duration
|
||
}
|
||
|
||
func Load() (*Config, error) <span class="cov0" title="0">{
|
||
// Загрузка .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"),
|
||
},
|
||
JWT: JWTConfig{
|
||
Secret: getEnv("JWT_SECRET", "your-secret-key"),
|
||
TTL: time.Duration(getEnvAsInt("JWT_TTL_HOURS", 24)) * time.Hour,
|
||
},
|
||
}, nil
|
||
}</span>
|
||
|
||
func getEnv(key, defaultValue string) string <span class="cov0" title="0">{
|
||
if value := os.Getenv(key); value != "" </span><span class="cov0" title="0">{
|
||
return value
|
||
}</span>
|
||
<span class="cov0" title="0">return defaultValue</span>
|
||
}
|
||
|
||
func getEnvAsInt(key string, defaultValue int) int <span class="cov0" title="0">{
|
||
if value := os.Getenv(key); value != "" </span><span class="cov0" title="0">{
|
||
if intValue, err := strconv.Atoi(value); err == nil </span><span class="cov0" title="0">{
|
||
return intValue
|
||
}</span>
|
||
}
|
||
<span class="cov0" title="0">return defaultValue</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file10" style="display: none">package database
|
||
|
||
import (
|
||
"database/sql"
|
||
"fmt"
|
||
|
||
"erp-mvp/core-service/internal/config"
|
||
_ "github.com/lib/pq"
|
||
)
|
||
|
||
func Connect(cfg config.DatabaseConfig) (*sql.DB, error) <span class="cov0" title="0">{
|
||
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
|
||
cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.DBName, cfg.SSLMode)
|
||
|
||
db, err := sql.Open("postgres", dsn)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to open database: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := db.Ping(); err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to ping database: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return db, nil</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file11" style="display: none">package logger
|
||
|
||
import (
|
||
"os"
|
||
|
||
"github.com/sirupsen/logrus"
|
||
)
|
||
|
||
type Logger interface {
|
||
Info(args ...interface{})
|
||
Error(args ...interface{})
|
||
Fatal(args ...interface{})
|
||
Debug(args ...interface{})
|
||
Warn(args ...interface{})
|
||
}
|
||
|
||
type logger struct {
|
||
*logrus.Logger
|
||
}
|
||
|
||
func New() Logger <span class="cov0" title="0">{
|
||
l := logrus.New()
|
||
l.SetOutput(os.Stdout)
|
||
l.SetLevel(logrus.InfoLevel)
|
||
l.SetFormatter(&logrus.JSONFormatter{})
|
||
|
||
return &logger{l}
|
||
}</span>
|
||
</pre>
|
||
|
||
<pre class="file" id="file12" style="display: none">package repository
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"fmt"
|
||
|
||
"erp-mvp/core-service/internal/models"
|
||
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
type ItemRepository interface {
|
||
Create(ctx context.Context, item *models.Item) error
|
||
GetByID(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.Item, error)
|
||
GetByOrganization(ctx context.Context, orgID uuid.UUID) ([]*models.Item, error)
|
||
Update(ctx context.Context, item *models.Item) error
|
||
Delete(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error
|
||
Search(ctx context.Context, orgID uuid.UUID, query string, category string) ([]*models.Item, error)
|
||
}
|
||
|
||
type itemRepository struct {
|
||
db *sql.DB
|
||
}
|
||
|
||
func NewItemRepository(db *sql.DB) ItemRepository <span class="cov8" title="1">{
|
||
return &itemRepository{db: db}
|
||
}</span>
|
||
|
||
func (r *itemRepository) Create(ctx context.Context, item *models.Item) error <span class="cov8" title="1">{
|
||
query := `
|
||
INSERT INTO items (id, organization_id, name, description, category, created_at)
|
||
VALUES ($1, $2, $3, $4, $5, $6)
|
||
`
|
||
|
||
_, err := r.db.ExecContext(ctx, query,
|
||
item.ID,
|
||
item.OrganizationID,
|
||
item.Name,
|
||
item.Description,
|
||
item.Category,
|
||
item.CreatedAt,
|
||
)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to create item: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov8" title="1">return nil</span>
|
||
}
|
||
|
||
func (r *itemRepository) GetByID(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.Item, error) <span class="cov0" title="0">{
|
||
query := `
|
||
SELECT id, organization_id, name, description, category, created_at
|
||
FROM items
|
||
WHERE id = $1 AND organization_id = $2
|
||
`
|
||
|
||
item := &models.Item{}
|
||
err := r.db.QueryRowContext(ctx, query, id, orgID).Scan(
|
||
&item.ID,
|
||
&item.OrganizationID,
|
||
&item.Name,
|
||
&item.Description,
|
||
&item.Category,
|
||
&item.CreatedAt,
|
||
)
|
||
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
if err == sql.ErrNoRows </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("item not found")
|
||
}</span>
|
||
<span class="cov0" title="0">return nil, fmt.Errorf("failed to get item: %w", err)</span>
|
||
}
|
||
|
||
<span class="cov0" title="0">return item, nil</span>
|
||
}
|
||
|
||
func (r *itemRepository) GetByOrganization(ctx context.Context, orgID uuid.UUID) ([]*models.Item, error) <span class="cov0" title="0">{
|
||
query := `
|
||
SELECT id, organization_id, name, description, category, created_at
|
||
FROM items
|
||
WHERE organization_id = $1
|
||
ORDER BY name
|
||
`
|
||
|
||
rows, err := r.db.QueryContext(ctx, query, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to query items: %w", err)
|
||
}</span>
|
||
<span class="cov0" title="0">defer rows.Close()
|
||
|
||
var items []*models.Item
|
||
for rows.Next() </span><span class="cov0" title="0">{
|
||
item := &models.Item{}
|
||
err := rows.Scan(
|
||
&item.ID,
|
||
&item.OrganizationID,
|
||
&item.Name,
|
||
&item.Description,
|
||
&item.Category,
|
||
&item.CreatedAt,
|
||
)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to scan item: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">items = append(items, item)</span>
|
||
}
|
||
|
||
<span class="cov0" title="0">if err = rows.Err(); err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("error iterating items: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return items, nil</span>
|
||
}
|
||
|
||
func (r *itemRepository) Update(ctx context.Context, item *models.Item) error <span class="cov0" title="0">{
|
||
query := `
|
||
UPDATE items
|
||
SET name = $3, description = $4, category = $5
|
||
WHERE id = $1 AND organization_id = $2
|
||
`
|
||
|
||
result, err := r.db.ExecContext(ctx, query,
|
||
item.ID,
|
||
item.OrganizationID,
|
||
item.Name,
|
||
item.Description,
|
||
item.Category,
|
||
)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to update item: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">rowsAffected, err := result.RowsAffected()
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to get rows affected: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if rowsAffected == 0 </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("item not found")
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return nil</span>
|
||
}
|
||
|
||
func (r *itemRepository) Delete(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error <span class="cov0" title="0">{
|
||
query := `
|
||
DELETE FROM items
|
||
WHERE id = $1 AND organization_id = $2
|
||
`
|
||
|
||
result, err := r.db.ExecContext(ctx, query, id, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to delete item: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">rowsAffected, err := result.RowsAffected()
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to get rows affected: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if rowsAffected == 0 </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("item not found")
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return nil</span>
|
||
}
|
||
|
||
func (r *itemRepository) Search(ctx context.Context, orgID uuid.UUID, query string, category string) ([]*models.Item, error) <span class="cov0" title="0">{
|
||
baseQuery := `
|
||
SELECT id, organization_id, name, description, category, created_at
|
||
FROM items
|
||
WHERE organization_id = $1
|
||
`
|
||
|
||
var args []interface{}
|
||
args = append(args, orgID)
|
||
argIndex := 2
|
||
|
||
if query != "" </span><span class="cov0" title="0">{
|
||
baseQuery += fmt.Sprintf(" AND (name ILIKE $%d OR description ILIKE $%d)", argIndex, argIndex)
|
||
args = append(args, "%"+query+"%")
|
||
argIndex++
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if category != "" </span><span class="cov0" title="0">{
|
||
baseQuery += fmt.Sprintf(" AND category = $%d", argIndex)
|
||
args = append(args, category)
|
||
argIndex++
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">baseQuery += " ORDER BY name"
|
||
|
||
rows, err := r.db.QueryContext(ctx, baseQuery, args...)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to search items: %w", err)
|
||
}</span>
|
||
<span class="cov0" title="0">defer rows.Close()
|
||
|
||
var items []*models.Item
|
||
for rows.Next() </span><span class="cov0" title="0">{
|
||
item := &models.Item{}
|
||
err := rows.Scan(
|
||
&item.ID,
|
||
&item.OrganizationID,
|
||
&item.Name,
|
||
&item.Description,
|
||
&item.Category,
|
||
&item.CreatedAt,
|
||
)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to scan item: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">items = append(items, item)</span>
|
||
}
|
||
|
||
<span class="cov0" title="0">if err = rows.Err(); err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("error iterating search results: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return items, nil</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file13" style="display: none">package repository
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"fmt"
|
||
|
||
"erp-mvp/core-service/internal/models"
|
||
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
type LocationRepository interface {
|
||
Create(ctx context.Context, location *models.StorageLocation) error
|
||
GetByID(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.StorageLocation, error)
|
||
GetByOrganization(ctx context.Context, orgID uuid.UUID) ([]*models.StorageLocation, error)
|
||
Update(ctx context.Context, location *models.StorageLocation) error
|
||
Delete(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error
|
||
GetChildren(ctx context.Context, parentID uuid.UUID, orgID uuid.UUID) ([]*models.StorageLocation, error)
|
||
}
|
||
|
||
type locationRepository struct {
|
||
db *sql.DB
|
||
}
|
||
|
||
func NewLocationRepository(db *sql.DB) LocationRepository <span class="cov8" title="1">{
|
||
return &locationRepository{db: db}
|
||
}</span>
|
||
|
||
func (r *locationRepository) Create(ctx context.Context, location *models.StorageLocation) error <span class="cov8" title="1">{
|
||
query := `
|
||
INSERT INTO storage_locations (id, organization_id, parent_id, name, address, type, coordinates, created_at)
|
||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||
`
|
||
|
||
// Конвертируем JSON в строку
|
||
var coordinatesJSON string
|
||
if location.Coordinates != nil </span><span class="cov8" title="1">{
|
||
coords, err := json.Marshal(location.Coordinates)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to marshal coordinates: %w", err)
|
||
}</span>
|
||
<span class="cov8" title="1">coordinatesJSON = string(coords)</span>
|
||
}
|
||
|
||
<span class="cov8" title="1">_, err := r.db.ExecContext(ctx, query,
|
||
location.ID,
|
||
location.OrganizationID,
|
||
location.ParentID,
|
||
location.Name,
|
||
location.Address,
|
||
location.Type,
|
||
coordinatesJSON,
|
||
location.CreatedAt,
|
||
)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to create storage location: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov8" title="1">return nil</span>
|
||
}
|
||
|
||
func (r *locationRepository) GetByID(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.StorageLocation, error) <span class="cov8" title="1">{
|
||
query := `
|
||
SELECT id, organization_id, parent_id, name, address, type, coordinates, created_at
|
||
FROM storage_locations
|
||
WHERE id = $1 AND organization_id = $2
|
||
`
|
||
|
||
var coordinatesJSON []byte
|
||
location := &models.StorageLocation{}
|
||
err := r.db.QueryRowContext(ctx, query, id, orgID).Scan(
|
||
&location.ID,
|
||
&location.OrganizationID,
|
||
&location.ParentID,
|
||
&location.Name,
|
||
&location.Address,
|
||
&location.Type,
|
||
&coordinatesJSON,
|
||
&location.CreatedAt,
|
||
)
|
||
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
if err == sql.ErrNoRows </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("storage location not found")
|
||
}</span>
|
||
<span class="cov0" title="0">return nil, fmt.Errorf("failed to get storage location: %w", err)</span>
|
||
}
|
||
|
||
// Конвертируем JSON строку в map
|
||
<span class="cov8" title="1">if len(coordinatesJSON) > 0 </span><span class="cov8" title="1">{
|
||
err = json.Unmarshal(coordinatesJSON, &location.Coordinates)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to unmarshal coordinates: %w", err)
|
||
}</span>
|
||
} else<span class="cov0" title="0"> {
|
||
location.Coordinates = make(models.JSON)
|
||
}</span>
|
||
|
||
<span class="cov8" title="1">return location, nil</span>
|
||
}
|
||
|
||
func (r *locationRepository) GetByOrganization(ctx context.Context, orgID uuid.UUID) ([]*models.StorageLocation, error) <span class="cov0" title="0">{
|
||
query := `
|
||
SELECT id, organization_id, parent_id, name, address, type, coordinates, created_at
|
||
FROM storage_locations
|
||
WHERE organization_id = $1
|
||
ORDER BY name
|
||
`
|
||
|
||
rows, err := r.db.QueryContext(ctx, query, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to query storage locations: %w", err)
|
||
}</span>
|
||
<span class="cov0" title="0">defer rows.Close()
|
||
|
||
var locations []*models.StorageLocation
|
||
for rows.Next() </span><span class="cov0" title="0">{
|
||
var coordinatesJSON []byte
|
||
location := &models.StorageLocation{}
|
||
err := rows.Scan(
|
||
&location.ID,
|
||
&location.OrganizationID,
|
||
&location.ParentID,
|
||
&location.Name,
|
||
&location.Address,
|
||
&location.Type,
|
||
&coordinatesJSON,
|
||
&location.CreatedAt,
|
||
)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to scan storage location: %w", err)
|
||
}</span>
|
||
|
||
// Конвертируем JSON строку в map
|
||
<span class="cov0" title="0">if len(coordinatesJSON) > 0 </span><span class="cov0" title="0">{
|
||
err = json.Unmarshal(coordinatesJSON, &location.Coordinates)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to unmarshal coordinates: %w", err)
|
||
}</span>
|
||
} else<span class="cov0" title="0"> {
|
||
location.Coordinates = make(models.JSON)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">locations = append(locations, location)</span>
|
||
}
|
||
|
||
<span class="cov0" title="0">if err = rows.Err(); err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("error iterating storage locations: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return locations, nil</span>
|
||
}
|
||
|
||
func (r *locationRepository) Update(ctx context.Context, location *models.StorageLocation) error <span class="cov0" title="0">{
|
||
query := `
|
||
UPDATE storage_locations
|
||
SET parent_id = $3, name = $4, address = $5, type = $6, coordinates = $7
|
||
WHERE id = $1 AND organization_id = $2
|
||
`
|
||
|
||
// Конвертируем JSON в строку
|
||
var coordinatesJSON string
|
||
if location.Coordinates != nil </span><span class="cov0" title="0">{
|
||
coords, err := json.Marshal(location.Coordinates)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to marshal coordinates: %w", err)
|
||
}</span>
|
||
<span class="cov0" title="0">coordinatesJSON = string(coords)</span>
|
||
}
|
||
|
||
<span class="cov0" title="0">result, err := r.db.ExecContext(ctx, query,
|
||
location.ID,
|
||
location.OrganizationID,
|
||
location.ParentID,
|
||
location.Name,
|
||
location.Address,
|
||
location.Type,
|
||
coordinatesJSON,
|
||
)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to update storage location: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">rowsAffected, err := result.RowsAffected()
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to get rows affected: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if rowsAffected == 0 </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("storage location not found")
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return nil</span>
|
||
}
|
||
|
||
func (r *locationRepository) Delete(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error <span class="cov0" title="0">{
|
||
query := `
|
||
DELETE FROM storage_locations
|
||
WHERE id = $1 AND organization_id = $2
|
||
`
|
||
|
||
result, err := r.db.ExecContext(ctx, query, id, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to delete storage location: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">rowsAffected, err := result.RowsAffected()
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to get rows affected: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if rowsAffected == 0 </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("storage location not found")
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return nil</span>
|
||
}
|
||
|
||
func (r *locationRepository) GetChildren(ctx context.Context, parentID uuid.UUID, orgID uuid.UUID) ([]*models.StorageLocation, error) <span class="cov0" title="0">{
|
||
query := `
|
||
SELECT id, organization_id, parent_id, name, address, type, coordinates, created_at
|
||
FROM storage_locations
|
||
WHERE parent_id = $1 AND organization_id = $2
|
||
ORDER BY name
|
||
`
|
||
|
||
rows, err := r.db.QueryContext(ctx, query, parentID, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to query child locations: %w", err)
|
||
}</span>
|
||
<span class="cov0" title="0">defer rows.Close()
|
||
|
||
var locations []*models.StorageLocation
|
||
for rows.Next() </span><span class="cov0" title="0">{
|
||
var coordinatesJSON []byte
|
||
location := &models.StorageLocation{}
|
||
err := rows.Scan(
|
||
&location.ID,
|
||
&location.OrganizationID,
|
||
&location.ParentID,
|
||
&location.Name,
|
||
&location.Address,
|
||
&location.Type,
|
||
&coordinatesJSON,
|
||
&location.CreatedAt,
|
||
)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to scan child location: %w", err)
|
||
}</span>
|
||
|
||
// Конвертируем JSON строку в map
|
||
<span class="cov0" title="0">if len(coordinatesJSON) > 0 </span><span class="cov0" title="0">{
|
||
err = json.Unmarshal(coordinatesJSON, &location.Coordinates)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to unmarshal coordinates: %w", err)
|
||
}</span>
|
||
} else<span class="cov0" title="0"> {
|
||
location.Coordinates = make(models.JSON)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">locations = append(locations, location)</span>
|
||
}
|
||
|
||
<span class="cov0" title="0">if err = rows.Err(); err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("error iterating child locations: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return locations, nil</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file14" style="display: none">package repository
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"fmt"
|
||
|
||
"erp-mvp/core-service/internal/models"
|
||
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
type OperationsRepository interface {
|
||
PlaceItem(ctx context.Context, placement *models.ItemPlacement) error
|
||
MoveItem(ctx context.Context, placementID uuid.UUID, newLocationID uuid.UUID, orgID uuid.UUID) error
|
||
GetByItem(ctx context.Context, itemID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error)
|
||
GetByLocation(ctx context.Context, locationID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error)
|
||
GetByID(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.ItemPlacement, error)
|
||
UpdateQuantity(ctx context.Context, id uuid.UUID, quantity int, orgID uuid.UUID) error
|
||
Delete(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error
|
||
Search(ctx context.Context, orgID uuid.UUID, query string, category string, address string) ([]*models.ItemWithLocation, error)
|
||
}
|
||
|
||
type operationsRepository struct {
|
||
db *sql.DB
|
||
}
|
||
|
||
func NewOperationsRepository(db *sql.DB) OperationsRepository <span class="cov8" title="1">{
|
||
return &operationsRepository{db: db}
|
||
}</span>
|
||
|
||
func (r *operationsRepository) PlaceItem(ctx context.Context, placement *models.ItemPlacement) error <span class="cov8" title="1">{
|
||
query := `
|
||
INSERT INTO item_placements (id, organization_id, item_id, location_id, quantity, created_at)
|
||
VALUES ($1, $2, $3, $4, $5, $6)
|
||
`
|
||
|
||
_, err := r.db.ExecContext(ctx, query,
|
||
placement.ID,
|
||
placement.OrganizationID,
|
||
placement.ItemID,
|
||
placement.LocationID,
|
||
placement.Quantity,
|
||
placement.CreatedAt,
|
||
)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to place item: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov8" title="1">return nil</span>
|
||
}
|
||
|
||
func (r *operationsRepository) MoveItem(ctx context.Context, placementID uuid.UUID, newLocationID uuid.UUID, orgID uuid.UUID) error <span class="cov0" title="0">{
|
||
query := `
|
||
UPDATE item_placements
|
||
SET location_id = $2
|
||
WHERE id = $1 AND organization_id = $3
|
||
`
|
||
|
||
result, err := r.db.ExecContext(ctx, query, placementID, newLocationID, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to move item: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">rowsAffected, err := result.RowsAffected()
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to get rows affected: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if rowsAffected == 0 </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("item placement not found")
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return nil</span>
|
||
}
|
||
|
||
func (r *operationsRepository) GetByItem(ctx context.Context, itemID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error) <span class="cov0" title="0">{
|
||
query := `
|
||
SELECT id, organization_id, item_id, location_id, quantity, created_at
|
||
FROM item_placements
|
||
WHERE item_id = $1 AND organization_id = $2
|
||
ORDER BY created_at DESC
|
||
`
|
||
|
||
rows, err := r.db.QueryContext(ctx, query, itemID, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to query item placements: %w", err)
|
||
}</span>
|
||
<span class="cov0" title="0">defer rows.Close()
|
||
|
||
var placements []*models.ItemPlacement
|
||
for rows.Next() </span><span class="cov0" title="0">{
|
||
placement := &models.ItemPlacement{}
|
||
err := rows.Scan(
|
||
&placement.ID,
|
||
&placement.OrganizationID,
|
||
&placement.ItemID,
|
||
&placement.LocationID,
|
||
&placement.Quantity,
|
||
&placement.CreatedAt,
|
||
)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to scan item placement: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">placements = append(placements, placement)</span>
|
||
}
|
||
|
||
<span class="cov0" title="0">if err = rows.Err(); err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("error iterating item placements: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return placements, nil</span>
|
||
}
|
||
|
||
func (r *operationsRepository) GetByLocation(ctx context.Context, locationID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error) <span class="cov0" title="0">{
|
||
query := `
|
||
SELECT id, organization_id, item_id, location_id, quantity, created_at
|
||
FROM item_placements
|
||
WHERE location_id = $1 AND organization_id = $2
|
||
ORDER BY created_at DESC
|
||
`
|
||
|
||
rows, err := r.db.QueryContext(ctx, query, locationID, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to query location placements: %w", err)
|
||
}</span>
|
||
<span class="cov0" title="0">defer rows.Close()
|
||
|
||
var placements []*models.ItemPlacement
|
||
for rows.Next() </span><span class="cov0" title="0">{
|
||
placement := &models.ItemPlacement{}
|
||
err := rows.Scan(
|
||
&placement.ID,
|
||
&placement.OrganizationID,
|
||
&placement.ItemID,
|
||
&placement.LocationID,
|
||
&placement.Quantity,
|
||
&placement.CreatedAt,
|
||
)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to scan item placement: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">placements = append(placements, placement)</span>
|
||
}
|
||
|
||
<span class="cov0" title="0">if err = rows.Err(); err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("error iterating location placements: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return placements, nil</span>
|
||
}
|
||
|
||
func (r *operationsRepository) GetByID(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.ItemPlacement, error) <span class="cov0" title="0">{
|
||
query := `
|
||
SELECT id, organization_id, item_id, location_id, quantity, created_at
|
||
FROM item_placements
|
||
WHERE id = $1 AND organization_id = $2
|
||
`
|
||
|
||
placement := &models.ItemPlacement{}
|
||
err := r.db.QueryRowContext(ctx, query, id, orgID).Scan(
|
||
&placement.ID,
|
||
&placement.OrganizationID,
|
||
&placement.ItemID,
|
||
&placement.LocationID,
|
||
&placement.Quantity,
|
||
&placement.CreatedAt,
|
||
)
|
||
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
if err == sql.ErrNoRows </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("item placement not found")
|
||
}</span>
|
||
<span class="cov0" title="0">return nil, fmt.Errorf("failed to get item placement: %w", err)</span>
|
||
}
|
||
|
||
<span class="cov0" title="0">return placement, nil</span>
|
||
}
|
||
|
||
func (r *operationsRepository) UpdateQuantity(ctx context.Context, id uuid.UUID, quantity int, orgID uuid.UUID) error <span class="cov0" title="0">{
|
||
query := `
|
||
UPDATE item_placements
|
||
SET quantity = $2
|
||
WHERE id = $1 AND organization_id = $3
|
||
`
|
||
|
||
result, err := r.db.ExecContext(ctx, query, id, quantity, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to update quantity: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">rowsAffected, err := result.RowsAffected()
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to get rows affected: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if rowsAffected == 0 </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("item placement not found")
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return nil</span>
|
||
}
|
||
|
||
func (r *operationsRepository) Delete(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error <span class="cov0" title="0">{
|
||
query := `
|
||
DELETE FROM item_placements
|
||
WHERE id = $1 AND organization_id = $2
|
||
`
|
||
|
||
result, err := r.db.ExecContext(ctx, query, id, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to delete item placement: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">rowsAffected, err := result.RowsAffected()
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to get rows affected: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if rowsAffected == 0 </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("item placement not found")
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return nil</span>
|
||
}
|
||
|
||
func (r *operationsRepository) Search(ctx context.Context, orgID uuid.UUID, query string, category string, address string) ([]*models.ItemWithLocation, error) <span class="cov8" title="1">{
|
||
baseQuery := `
|
||
SELECT
|
||
i.id, i.organization_id, i.name, i.description, i.category, i.created_at,
|
||
sl.id, sl.organization_id, sl.parent_id, sl.name, sl.address, sl.type, sl.coordinates, sl.created_at,
|
||
ip.quantity
|
||
FROM items i
|
||
JOIN item_placements ip ON i.id = ip.item_id
|
||
JOIN storage_locations sl ON ip.location_id = sl.id
|
||
WHERE i.organization_id = $1 AND sl.organization_id = $1
|
||
`
|
||
|
||
var args []interface{}
|
||
args = append(args, orgID)
|
||
argIndex := 2
|
||
|
||
if query != "" </span><span class="cov8" title="1">{
|
||
baseQuery += fmt.Sprintf(" AND (i.name ILIKE $%d OR i.description ILIKE $%d)", argIndex, argIndex)
|
||
args = append(args, "%"+query+"%")
|
||
argIndex++
|
||
}</span>
|
||
|
||
<span class="cov8" title="1">if category != "" </span><span class="cov0" title="0">{
|
||
baseQuery += fmt.Sprintf(" AND i.category = $%d", argIndex)
|
||
args = append(args, category)
|
||
argIndex++
|
||
}</span>
|
||
|
||
<span class="cov8" title="1">if address != "" </span><span class="cov0" title="0">{
|
||
baseQuery += fmt.Sprintf(" AND sl.address ILIKE $%d", argIndex)
|
||
args = append(args, "%"+address+"%")
|
||
argIndex++
|
||
}</span>
|
||
|
||
<span class="cov8" title="1">baseQuery += " ORDER BY i.name, sl.name"
|
||
|
||
rows, err := r.db.QueryContext(ctx, baseQuery, args...)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to search items with locations: %w", err)
|
||
}</span>
|
||
<span class="cov8" title="1">defer rows.Close()
|
||
|
||
var results []*models.ItemWithLocation
|
||
for rows.Next() </span><span class="cov8" title="1">{
|
||
var coordinatesJSON []byte
|
||
itemWithLocation := &models.ItemWithLocation{}
|
||
|
||
err := rows.Scan(
|
||
&itemWithLocation.Item.ID,
|
||
&itemWithLocation.Item.OrganizationID,
|
||
&itemWithLocation.Item.Name,
|
||
&itemWithLocation.Item.Description,
|
||
&itemWithLocation.Item.Category,
|
||
&itemWithLocation.Item.CreatedAt,
|
||
&itemWithLocation.Location.ID,
|
||
&itemWithLocation.Location.OrganizationID,
|
||
&itemWithLocation.Location.ParentID,
|
||
&itemWithLocation.Location.Name,
|
||
&itemWithLocation.Location.Address,
|
||
&itemWithLocation.Location.Type,
|
||
&coordinatesJSON,
|
||
&itemWithLocation.Location.CreatedAt,
|
||
&itemWithLocation.Quantity,
|
||
)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to scan item with location: %w", err)
|
||
}</span>
|
||
|
||
// Конвертируем JSON строку в map
|
||
<span class="cov8" title="1">if len(coordinatesJSON) > 0 </span><span class="cov8" title="1">{
|
||
err = json.Unmarshal(coordinatesJSON, &itemWithLocation.Location.Coordinates)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to unmarshal coordinates: %w", err)
|
||
}</span>
|
||
} else<span class="cov0" title="0"> {
|
||
itemWithLocation.Location.Coordinates = make(models.JSON)
|
||
}</span>
|
||
|
||
<span class="cov8" title="1">results = append(results, itemWithLocation)</span>
|
||
}
|
||
|
||
<span class="cov8" title="1">if err = rows.Err(); err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("error iterating search results: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov8" title="1">return results, nil</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file15" style="display: none">package repository
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"fmt"
|
||
|
||
"erp-mvp/core-service/internal/models"
|
||
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
type OrganizationRepository interface {
|
||
Create(ctx context.Context, org *models.Organization) error
|
||
GetByID(ctx context.Context, id uuid.UUID) (*models.Organization, error)
|
||
Update(ctx context.Context, org *models.Organization) error
|
||
}
|
||
|
||
type organizationRepository struct {
|
||
db *sql.DB
|
||
}
|
||
|
||
func NewOrganizationRepository(db *sql.DB) OrganizationRepository <span class="cov8" title="1">{
|
||
return &organizationRepository{db: db}
|
||
}</span>
|
||
|
||
func (r *organizationRepository) Create(ctx context.Context, org *models.Organization) error <span class="cov8" title="1">{
|
||
query := `
|
||
INSERT INTO organizations (id, name, type, settings, created_at)
|
||
VALUES ($1, $2, $3, $4, $5)
|
||
`
|
||
|
||
// Конвертируем JSON в строку
|
||
settingsJSON, err := json.Marshal(org.Settings)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to marshal settings: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov8" title="1">_, err = r.db.ExecContext(ctx, query, org.ID, org.Name, org.Type, string(settingsJSON), org.CreatedAt)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to create organization: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov8" title="1">return nil</span>
|
||
}
|
||
|
||
func (r *organizationRepository) GetByID(ctx context.Context, id uuid.UUID) (*models.Organization, error) <span class="cov8" title="1">{
|
||
query := `
|
||
SELECT id, name, type, settings, created_at
|
||
FROM organizations
|
||
WHERE id = $1
|
||
`
|
||
|
||
var settingsJSON []byte
|
||
org := &models.Organization{}
|
||
err := r.db.QueryRowContext(ctx, query, id).Scan(
|
||
&org.ID,
|
||
&org.Name,
|
||
&org.Type,
|
||
&settingsJSON,
|
||
&org.CreatedAt,
|
||
)
|
||
|
||
if err != nil </span><span class="cov8" title="1">{
|
||
if err == sql.ErrNoRows </span><span class="cov8" title="1">{
|
||
return nil, fmt.Errorf("organization not found")
|
||
}</span>
|
||
<span class="cov0" title="0">return nil, fmt.Errorf("failed to get organization: %w", err)</span>
|
||
}
|
||
|
||
// Конвертируем JSON строку в map
|
||
<span class="cov8" title="1">if len(settingsJSON) > 0 </span><span class="cov8" title="1">{
|
||
err = json.Unmarshal(settingsJSON, &org.Settings)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("failed to unmarshal settings: %w", err)
|
||
}</span>
|
||
} else<span class="cov0" title="0"> {
|
||
org.Settings = make(models.JSON)
|
||
}</span>
|
||
|
||
<span class="cov8" title="1">return org, nil</span>
|
||
}
|
||
|
||
func (r *organizationRepository) Update(ctx context.Context, org *models.Organization) error <span class="cov0" title="0">{
|
||
query := `
|
||
UPDATE organizations
|
||
SET name = $2, type = $3, settings = $4
|
||
WHERE id = $1
|
||
`
|
||
|
||
// Конвертируем JSON в строку
|
||
settingsJSON, err := json.Marshal(org.Settings)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to marshal settings: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">result, err := r.db.ExecContext(ctx, query, org.ID, org.Name, org.Type, string(settingsJSON))
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to update organization: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">rowsAffected, err := result.RowsAffected()
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to get rows affected: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if rowsAffected == 0 </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("organization not found")
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return nil</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file16" style="display: none">package repository
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"fmt"
|
||
|
||
"erp-mvp/core-service/internal/models"
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
type UserRepository interface {
|
||
Create(ctx context.Context, user *models.User, password string) error
|
||
GetByEmail(ctx context.Context, email string) (*models.User, error)
|
||
GetByID(ctx context.Context, id uuid.UUID) (*models.User, error)
|
||
}
|
||
|
||
type userRepository struct {
|
||
db *sql.DB
|
||
}
|
||
|
||
func NewUserRepository(db *sql.DB) UserRepository <span class="cov8" title="1">{
|
||
return &userRepository{db: db}
|
||
}</span>
|
||
|
||
func (r *userRepository) Create(ctx context.Context, user *models.User, password string) error <span class="cov8" title="1">{
|
||
query := `
|
||
INSERT INTO users (id, organization_id, email, password_hash, role, created_at)
|
||
VALUES ($1, $2, $3, $4, $5, $6)
|
||
`
|
||
|
||
_, err := r.db.ExecContext(ctx, query,
|
||
user.ID,
|
||
user.OrganizationID,
|
||
user.Email,
|
||
password,
|
||
user.Role,
|
||
user.CreatedAt,
|
||
)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return fmt.Errorf("failed to create user: %w", err)
|
||
}</span>
|
||
|
||
<span class="cov8" title="1">return nil</span>
|
||
}
|
||
|
||
func (r *userRepository) GetByEmail(ctx context.Context, email string) (*models.User, error) <span class="cov8" title="1">{
|
||
query := `
|
||
SELECT id, organization_id, email, password_hash, role, created_at
|
||
FROM users
|
||
WHERE email = $1
|
||
`
|
||
|
||
user := &models.User{}
|
||
err := r.db.QueryRowContext(ctx, query, email).Scan(
|
||
&user.ID,
|
||
&user.OrganizationID,
|
||
&user.Email,
|
||
&user.PasswordHash,
|
||
&user.Role,
|
||
&user.CreatedAt,
|
||
)
|
||
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
if err == sql.ErrNoRows </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("user not found")
|
||
}</span>
|
||
<span class="cov0" title="0">return nil, fmt.Errorf("failed to get user: %w", err)</span>
|
||
}
|
||
|
||
<span class="cov8" title="1">return user, nil</span>
|
||
}
|
||
|
||
func (r *userRepository) GetByID(ctx context.Context, id uuid.UUID) (*models.User, error) <span class="cov0" title="0">{
|
||
query := `
|
||
SELECT id, organization_id, email, password_hash, role, created_at
|
||
FROM users
|
||
WHERE id = $1
|
||
`
|
||
|
||
user := &models.User{}
|
||
err := r.db.QueryRowContext(ctx, query, id).Scan(
|
||
&user.ID,
|
||
&user.OrganizationID,
|
||
&user.Email,
|
||
&user.PasswordHash,
|
||
&user.Role,
|
||
&user.CreatedAt,
|
||
)
|
||
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
if err == sql.ErrNoRows </span><span class="cov0" title="0">{
|
||
return nil, fmt.Errorf("user not found")
|
||
}</span>
|
||
<span class="cov0" title="0">return nil, fmt.Errorf("failed to get user: %w", err)</span>
|
||
}
|
||
|
||
<span class="cov0" title="0">return user, nil</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file17" style="display: none">package service
|
||
|
||
import (
|
||
"context"
|
||
"time"
|
||
|
||
"erp-mvp/core-service/internal/auth"
|
||
"erp-mvp/core-service/internal/models"
|
||
"erp-mvp/core-service/internal/repository"
|
||
|
||
"github.com/google/uuid"
|
||
"github.com/sirupsen/logrus"
|
||
)
|
||
|
||
type AuthService interface {
|
||
Register(ctx context.Context, req *models.RegisterRequest) (*models.LoginResponse, error)
|
||
Login(ctx context.Context, req *models.LoginRequest) (*models.LoginResponse, error)
|
||
}
|
||
|
||
type authService struct {
|
||
orgRepo repository.OrganizationRepository
|
||
userRepo repository.UserRepository
|
||
jwtService *auth.JWTService
|
||
logger *logrus.Logger
|
||
}
|
||
|
||
func NewAuthService(orgRepo repository.OrganizationRepository, userRepo repository.UserRepository, jwtService *auth.JWTService) AuthService <span class="cov0" title="0">{
|
||
return &authService{
|
||
orgRepo: orgRepo,
|
||
userRepo: userRepo,
|
||
jwtService: jwtService,
|
||
logger: logrus.New(),
|
||
}
|
||
}</span>
|
||
|
||
func (s *authService) Register(ctx context.Context, req *models.RegisterRequest) (*models.LoginResponse, error) <span class="cov0" title="0">{
|
||
s.logger.Info("Starting registration process")
|
||
|
||
// Проверяем, что пользователь с таким email не существует
|
||
existingUser, err := s.userRepo.GetByEmail(ctx, req.UserEmail)
|
||
if err == nil && existingUser != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("User with this email already exists")
|
||
return nil, &ValidationError{Message: "User with this email already exists"}
|
||
}</span>
|
||
|
||
// Создаем организацию
|
||
<span class="cov0" title="0">orgID := uuid.New()
|
||
org := &models.Organization{
|
||
ID: orgID,
|
||
Name: req.OrganizationName,
|
||
Type: req.OrganizationType,
|
||
Settings: models.JSON{"created_at": time.Now().Unix()},
|
||
CreatedAt: time.Now(),
|
||
}
|
||
|
||
s.logger.Info("Creating organization with ID: ", orgID)
|
||
if err := s.orgRepo.Create(ctx, org); err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to create organization: ", err)
|
||
return nil, err
|
||
}</span>
|
||
<span class="cov0" title="0">s.logger.Info("Organization created successfully")
|
||
|
||
// Хешируем пароль
|
||
passwordHash, err := auth.HashPassword(req.UserPassword)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to hash password: ", err)
|
||
return nil, err
|
||
}</span>
|
||
<span class="cov0" title="0">s.logger.Info("Password hashed successfully")
|
||
|
||
// Создаем пользователя
|
||
userID := uuid.New()
|
||
user := &models.User{
|
||
ID: userID,
|
||
OrganizationID: orgID,
|
||
Email: req.UserEmail,
|
||
Role: "admin", // Первый пользователь становится админом
|
||
CreatedAt: time.Now(),
|
||
}
|
||
|
||
s.logger.Info("Creating user with ID: ", userID)
|
||
if err := s.userRepo.Create(ctx, user, passwordHash); err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to create user: ", err)
|
||
return nil, err
|
||
}</span>
|
||
<span class="cov0" title="0">s.logger.Info("User created successfully")
|
||
|
||
// Генерируем JWT токен
|
||
token, err := s.jwtService.GenerateToken(user.ID, org.ID, user.Email, user.Role)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to generate token: ", err)
|
||
return nil, err
|
||
}</span>
|
||
<span class="cov0" title="0">s.logger.Info("JWT token generated successfully")
|
||
|
||
return &models.LoginResponse{
|
||
Token: token,
|
||
User: models.UserResponse{
|
||
ID: user.ID,
|
||
Email: user.Email,
|
||
Role: user.Role,
|
||
},
|
||
Organization: models.OrganizationResponse{
|
||
ID: org.ID,
|
||
Name: org.Name,
|
||
Type: org.Type,
|
||
},
|
||
}, nil</span>
|
||
}
|
||
|
||
func (s *authService) Login(ctx context.Context, req *models.LoginRequest) (*models.LoginResponse, error) <span class="cov0" title="0">{
|
||
// Получаем пользователя по email
|
||
user, err := s.userRepo.GetByEmail(ctx, req.Email)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, &ValidationError{Message: "Invalid email or password"}
|
||
}</span>
|
||
|
||
// Проверяем пароль
|
||
<span class="cov0" title="0">if !auth.CheckPassword(req.Password, user.PasswordHash) </span><span class="cov0" title="0">{
|
||
return nil, &ValidationError{Message: "Invalid email or password"}
|
||
}</span>
|
||
|
||
// Генерируем JWT токен
|
||
<span class="cov0" title="0">token, err := s.jwtService.GenerateToken(user.ID, user.OrganizationID, user.Email, user.Role)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, err
|
||
}</span>
|
||
|
||
// Получаем организацию для ответа
|
||
<span class="cov0" title="0">org, err := s.orgRepo.GetByID(ctx, user.OrganizationID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return &models.LoginResponse{
|
||
Token: token,
|
||
User: models.UserResponse{
|
||
ID: user.ID,
|
||
Email: user.Email,
|
||
Role: user.Role,
|
||
},
|
||
Organization: models.OrganizationResponse{
|
||
ID: org.ID,
|
||
Name: org.Name,
|
||
Type: org.Type,
|
||
},
|
||
}, nil</span>
|
||
}
|
||
|
||
// ValidationError ошибка валидации
|
||
type ValidationError struct {
|
||
Message string
|
||
}
|
||
|
||
func (e *ValidationError) Error() string <span class="cov0" title="0">{
|
||
return e.Message
|
||
}</span>
|
||
</pre>
|
||
|
||
<pre class="file" id="file18" style="display: none">package service
|
||
|
||
import (
|
||
"context"
|
||
"time"
|
||
|
||
"erp-mvp/core-service/internal/models"
|
||
"erp-mvp/core-service/internal/repository"
|
||
|
||
"github.com/google/uuid"
|
||
"github.com/sirupsen/logrus"
|
||
)
|
||
|
||
type ItemService interface {
|
||
CreateItem(ctx context.Context, orgID uuid.UUID, req *models.CreateItemRequest) (*models.Item, error)
|
||
GetItem(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.Item, error)
|
||
GetItems(ctx context.Context, orgID uuid.UUID) ([]*models.Item, error)
|
||
UpdateItem(ctx context.Context, id uuid.UUID, orgID uuid.UUID, req *models.CreateItemRequest) (*models.Item, error)
|
||
DeleteItem(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error
|
||
SearchItems(ctx context.Context, orgID uuid.UUID, query string, category string) ([]*models.Item, error)
|
||
}
|
||
|
||
type itemService struct {
|
||
itemRepo repository.ItemRepository
|
||
logger *logrus.Logger
|
||
}
|
||
|
||
func NewItemService(itemRepo repository.ItemRepository) ItemService <span class="cov0" title="0">{
|
||
return &itemService{
|
||
itemRepo: itemRepo,
|
||
logger: logrus.New(),
|
||
}
|
||
}</span>
|
||
|
||
func (s *itemService) CreateItem(ctx context.Context, orgID uuid.UUID, req *models.CreateItemRequest) (*models.Item, error) <span class="cov0" title="0">{
|
||
s.logger.Info("Creating item for organization: ", orgID)
|
||
|
||
item := &models.Item{
|
||
ID: uuid.New(),
|
||
OrganizationID: orgID,
|
||
Name: req.Name,
|
||
Description: req.Description,
|
||
Category: req.Category,
|
||
CreatedAt: time.Now(),
|
||
}
|
||
|
||
if err := s.itemRepo.Create(ctx, item); err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to create item: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">s.logger.Info("Item created successfully: ", item.ID)
|
||
return item, nil</span>
|
||
}
|
||
|
||
func (s *itemService) GetItem(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.Item, error) <span class="cov0" title="0">{
|
||
s.logger.Info("Getting item: ", id, " for organization: ", orgID)
|
||
|
||
item, err := s.itemRepo.GetByID(ctx, id, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to get item: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return item, nil</span>
|
||
}
|
||
|
||
func (s *itemService) GetItems(ctx context.Context, orgID uuid.UUID) ([]*models.Item, error) <span class="cov0" title="0">{
|
||
s.logger.Info("Getting all items for organization: ", orgID)
|
||
|
||
items, err := s.itemRepo.GetByOrganization(ctx, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to get items: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return items, nil</span>
|
||
}
|
||
|
||
func (s *itemService) UpdateItem(ctx context.Context, id uuid.UUID, orgID uuid.UUID, req *models.CreateItemRequest) (*models.Item, error) <span class="cov0" title="0">{
|
||
s.logger.Info("Updating item: ", id, " for organization: ", orgID)
|
||
|
||
// Сначала получаем существующий товар
|
||
item, err := s.itemRepo.GetByID(ctx, id, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to get item for update: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
// Обновляем поля
|
||
<span class="cov0" title="0">item.Name = req.Name
|
||
item.Description = req.Description
|
||
item.Category = req.Category
|
||
|
||
if err := s.itemRepo.Update(ctx, item); err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to update item: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">s.logger.Info("Item updated successfully: ", item.ID)
|
||
return item, nil</span>
|
||
}
|
||
|
||
func (s *itemService) DeleteItem(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error <span class="cov0" title="0">{
|
||
s.logger.Info("Deleting item: ", id, " for organization: ", orgID)
|
||
|
||
if err := s.itemRepo.Delete(ctx, id, orgID); err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to delete item: ", err)
|
||
return err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">s.logger.Info("Item deleted successfully: ", id)
|
||
return nil</span>
|
||
}
|
||
|
||
func (s *itemService) SearchItems(ctx context.Context, orgID uuid.UUID, query string, category string) ([]*models.Item, error) <span class="cov0" title="0">{
|
||
s.logger.Info("Searching items for organization: ", orgID, " query: ", query, " category: ", category)
|
||
|
||
items, err := s.itemRepo.Search(ctx, orgID, query, category)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to search items: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return items, nil</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file19" style="display: none">package service
|
||
|
||
import (
|
||
"context"
|
||
"time"
|
||
|
||
"erp-mvp/core-service/internal/models"
|
||
"erp-mvp/core-service/internal/repository"
|
||
|
||
"github.com/google/uuid"
|
||
"github.com/sirupsen/logrus"
|
||
)
|
||
|
||
type LocationService interface {
|
||
CreateLocation(ctx context.Context, orgID uuid.UUID, req *models.CreateLocationRequest) (*models.StorageLocation, error)
|
||
GetLocation(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.StorageLocation, error)
|
||
GetLocations(ctx context.Context, orgID uuid.UUID) ([]*models.StorageLocation, error)
|
||
UpdateLocation(ctx context.Context, id uuid.UUID, orgID uuid.UUID, req *models.CreateLocationRequest) (*models.StorageLocation, error)
|
||
DeleteLocation(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error
|
||
GetChildren(ctx context.Context, parentID uuid.UUID, orgID uuid.UUID) ([]*models.StorageLocation, error)
|
||
}
|
||
|
||
type locationService struct {
|
||
locationRepo repository.LocationRepository
|
||
logger *logrus.Logger
|
||
}
|
||
|
||
func NewLocationService(locationRepo repository.LocationRepository) LocationService <span class="cov0" title="0">{
|
||
return &locationService{
|
||
locationRepo: locationRepo,
|
||
logger: logrus.New(),
|
||
}
|
||
}</span>
|
||
|
||
func (s *locationService) CreateLocation(ctx context.Context, orgID uuid.UUID, req *models.CreateLocationRequest) (*models.StorageLocation, error) <span class="cov0" title="0">{
|
||
s.logger.Info("Creating location for organization: ", orgID)
|
||
|
||
location := &models.StorageLocation{
|
||
ID: uuid.New(),
|
||
OrganizationID: orgID,
|
||
ParentID: req.ParentID,
|
||
Name: req.Name,
|
||
Address: req.Address,
|
||
Type: req.Type,
|
||
Coordinates: req.Coordinates,
|
||
CreatedAt: time.Now(),
|
||
}
|
||
|
||
if err := s.locationRepo.Create(ctx, location); err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to create location: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">s.logger.Info("Location created successfully: ", location.ID)
|
||
return location, nil</span>
|
||
}
|
||
|
||
func (s *locationService) GetLocation(ctx context.Context, id uuid.UUID, orgID uuid.UUID) (*models.StorageLocation, error) <span class="cov0" title="0">{
|
||
s.logger.Info("Getting location: ", id, " for organization: ", orgID)
|
||
|
||
location, err := s.locationRepo.GetByID(ctx, id, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to get location: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return location, nil</span>
|
||
}
|
||
|
||
func (s *locationService) GetLocations(ctx context.Context, orgID uuid.UUID) ([]*models.StorageLocation, error) <span class="cov0" title="0">{
|
||
s.logger.Info("Getting all locations for organization: ", orgID)
|
||
|
||
locations, err := s.locationRepo.GetByOrganization(ctx, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to get locations: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return locations, nil</span>
|
||
}
|
||
|
||
func (s *locationService) UpdateLocation(ctx context.Context, id uuid.UUID, orgID uuid.UUID, req *models.CreateLocationRequest) (*models.StorageLocation, error) <span class="cov0" title="0">{
|
||
s.logger.Info("Updating location: ", id, " for organization: ", orgID)
|
||
|
||
// Сначала получаем существующую локацию
|
||
location, err := s.locationRepo.GetByID(ctx, id, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to get location for update: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
// Обновляем поля
|
||
<span class="cov0" title="0">location.ParentID = req.ParentID
|
||
location.Name = req.Name
|
||
location.Address = req.Address
|
||
location.Type = req.Type
|
||
location.Coordinates = req.Coordinates
|
||
|
||
if err := s.locationRepo.Update(ctx, location); err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to update location: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">s.logger.Info("Location updated successfully: ", location.ID)
|
||
return location, nil</span>
|
||
}
|
||
|
||
func (s *locationService) DeleteLocation(ctx context.Context, id uuid.UUID, orgID uuid.UUID) error <span class="cov0" title="0">{
|
||
s.logger.Info("Deleting location: ", id, " for organization: ", orgID)
|
||
|
||
if err := s.locationRepo.Delete(ctx, id, orgID); err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to delete location: ", err)
|
||
return err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">s.logger.Info("Location deleted successfully: ", id)
|
||
return nil</span>
|
||
}
|
||
|
||
func (s *locationService) GetChildren(ctx context.Context, parentID uuid.UUID, orgID uuid.UUID) ([]*models.StorageLocation, error) <span class="cov0" title="0">{
|
||
s.logger.Info("Getting children for location: ", parentID, " in organization: ", orgID)
|
||
|
||
children, err := s.locationRepo.GetChildren(ctx, parentID, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to get children: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return children, nil</span>
|
||
}
|
||
</pre>
|
||
|
||
<pre class="file" id="file20" style="display: none">package service
|
||
|
||
import (
|
||
"context"
|
||
"time"
|
||
|
||
"erp-mvp/core-service/internal/models"
|
||
"erp-mvp/core-service/internal/repository"
|
||
|
||
"github.com/google/uuid"
|
||
"github.com/sirupsen/logrus"
|
||
)
|
||
|
||
type OperationsService interface {
|
||
PlaceItem(ctx context.Context, orgID uuid.UUID, req *models.PlaceItemRequest) (*models.ItemPlacement, error)
|
||
MoveItem(ctx context.Context, placementID uuid.UUID, newLocationID uuid.UUID, orgID uuid.UUID) error
|
||
GetItemPlacements(ctx context.Context, itemID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error)
|
||
GetLocationPlacements(ctx context.Context, locationID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error)
|
||
UpdateQuantity(ctx context.Context, placementID uuid.UUID, quantity int, orgID uuid.UUID) error
|
||
DeletePlacement(ctx context.Context, placementID uuid.UUID, orgID uuid.UUID) error
|
||
Search(ctx context.Context, orgID uuid.UUID, req *models.SearchRequest) (*models.SearchResponse, error)
|
||
}
|
||
|
||
type operationsService struct {
|
||
operationsRepo repository.OperationsRepository
|
||
itemRepo repository.ItemRepository
|
||
locationRepo repository.LocationRepository
|
||
logger *logrus.Logger
|
||
}
|
||
|
||
func NewOperationsService(operationsRepo repository.OperationsRepository, itemRepo repository.ItemRepository, locationRepo repository.LocationRepository) OperationsService <span class="cov0" title="0">{
|
||
return &operationsService{
|
||
operationsRepo: operationsRepo,
|
||
itemRepo: itemRepo,
|
||
locationRepo: locationRepo,
|
||
logger: logrus.New(),
|
||
}
|
||
}</span>
|
||
|
||
func (s *operationsService) PlaceItem(ctx context.Context, orgID uuid.UUID, req *models.PlaceItemRequest) (*models.ItemPlacement, error) <span class="cov0" title="0">{
|
||
s.logger.Info("Placing item: ", req.ItemID, " in location: ", req.LocationID, " for organization: ", orgID)
|
||
|
||
// Проверяем, что товар существует и принадлежит организации
|
||
_, err := s.itemRepo.GetByID(ctx, req.ItemID, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Item not found or not accessible: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
// Проверяем, что место хранения существует и принадлежит организации
|
||
<span class="cov0" title="0">_, err = s.locationRepo.GetByID(ctx, req.LocationID, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Location not found or not accessible: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">placement := &models.ItemPlacement{
|
||
ID: uuid.New(),
|
||
OrganizationID: orgID,
|
||
ItemID: req.ItemID,
|
||
LocationID: req.LocationID,
|
||
Quantity: req.Quantity,
|
||
CreatedAt: time.Now(),
|
||
}
|
||
|
||
if err := s.operationsRepo.PlaceItem(ctx, placement); err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to place item: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">s.logger.Info("Item placed successfully: ", placement.ID)
|
||
return placement, nil</span>
|
||
}
|
||
|
||
func (s *operationsService) MoveItem(ctx context.Context, placementID uuid.UUID, newLocationID uuid.UUID, orgID uuid.UUID) error <span class="cov0" title="0">{
|
||
s.logger.Info("Moving item placement: ", placementID, " to location: ", newLocationID, " for organization: ", orgID)
|
||
|
||
// Проверяем, что размещение существует и принадлежит организации
|
||
placement, err := s.operationsRepo.GetByID(ctx, placementID, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Item placement not found or not accessible: ", err)
|
||
return err
|
||
}</span>
|
||
|
||
// Проверяем, что новое место хранения существует и принадлежит организации
|
||
<span class="cov0" title="0">_, err = s.locationRepo.GetByID(ctx, newLocationID, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("New location not found or not accessible: ", err)
|
||
return err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := s.operationsRepo.MoveItem(ctx, placementID, newLocationID, orgID); err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to move item: ", err)
|
||
return err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">s.logger.Info("Item moved successfully from location: ", placement.LocationID, " to: ", newLocationID)
|
||
return nil</span>
|
||
}
|
||
|
||
func (s *operationsService) GetItemPlacements(ctx context.Context, itemID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error) <span class="cov0" title="0">{
|
||
s.logger.Info("Getting placements for item: ", itemID, " in organization: ", orgID)
|
||
|
||
// Проверяем, что товар существует и принадлежит организации
|
||
_, err := s.itemRepo.GetByID(ctx, itemID, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Item not found or not accessible: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">placements, err := s.operationsRepo.GetByItem(ctx, itemID, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to get item placements: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return placements, nil</span>
|
||
}
|
||
|
||
func (s *operationsService) GetLocationPlacements(ctx context.Context, locationID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error) <span class="cov0" title="0">{
|
||
s.logger.Info("Getting placements for location: ", locationID, " in organization: ", orgID)
|
||
|
||
// Проверяем, что место хранения существует и принадлежит организации
|
||
_, err := s.locationRepo.GetByID(ctx, locationID, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Location not found or not accessible: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">placements, err := s.operationsRepo.GetByLocation(ctx, locationID, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to get location placements: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">return placements, nil</span>
|
||
}
|
||
|
||
func (s *operationsService) UpdateQuantity(ctx context.Context, placementID uuid.UUID, quantity int, orgID uuid.UUID) error <span class="cov0" title="0">{
|
||
s.logger.Info("Updating quantity for placement: ", placementID, " to: ", quantity, " in organization: ", orgID)
|
||
|
||
// Проверяем, что размещение существует и принадлежит организации
|
||
_, err := s.operationsRepo.GetByID(ctx, placementID, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Item placement not found or not accessible: ", err)
|
||
return err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := s.operationsRepo.UpdateQuantity(ctx, placementID, quantity, orgID); err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to update quantity: ", err)
|
||
return err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">s.logger.Info("Quantity updated successfully for placement: ", placementID)
|
||
return nil</span>
|
||
}
|
||
|
||
func (s *operationsService) DeletePlacement(ctx context.Context, placementID uuid.UUID, orgID uuid.UUID) error <span class="cov0" title="0">{
|
||
s.logger.Info("Deleting placement: ", placementID, " for organization: ", orgID)
|
||
|
||
// Проверяем, что размещение существует и принадлежит организации
|
||
_, err := s.operationsRepo.GetByID(ctx, placementID, orgID)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Item placement not found or not accessible: ", err)
|
||
return err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">if err := s.operationsRepo.Delete(ctx, placementID, orgID); err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to delete placement: ", err)
|
||
return err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">s.logger.Info("Placement deleted successfully: ", placementID)
|
||
return nil</span>
|
||
}
|
||
|
||
func (s *operationsService) Search(ctx context.Context, orgID uuid.UUID, req *models.SearchRequest) (*models.SearchResponse, error) <span class="cov0" title="0">{
|
||
s.logger.Info("Searching items with locations for organization: ", orgID, " query: ", req.Query)
|
||
|
||
results, err := s.operationsRepo.Search(ctx, orgID, req.Query, req.Category, req.Address)
|
||
if err != nil </span><span class="cov0" title="0">{
|
||
s.logger.Error("Failed to search items with locations: ", err)
|
||
return nil, err
|
||
}</span>
|
||
|
||
<span class="cov0" title="0">response := &models.SearchResponse{
|
||
Items: results,
|
||
TotalCount: len(results),
|
||
}
|
||
|
||
return response, nil</span>
|
||
}
|
||
</pre>
|
||
|
||
</div>
|
||
</body>
|
||
<script>
|
||
(function() {
|
||
var files = document.getElementById('files');
|
||
var visible;
|
||
files.addEventListener('change', onChange, false);
|
||
function select(part) {
|
||
if (visible)
|
||
visible.style.display = 'none';
|
||
visible = document.getElementById(part);
|
||
if (!visible)
|
||
return;
|
||
files.value = part;
|
||
visible.style.display = 'block';
|
||
location.hash = part;
|
||
}
|
||
function onChange() {
|
||
select(files.value);
|
||
window.scrollTo(0, 0);
|
||
}
|
||
if (location.hash != "") {
|
||
select(location.hash.substr(1));
|
||
}
|
||
if (!visible) {
|
||
select("file0");
|
||
}
|
||
})();
|
||
</script>
|
||
</html>
|