Files
Mini-ERP-app/core-service/coverage.html
Andrey Epifantsev c8224f072a feat: добавлены инструменты анализа покрытия тестами
- Создан скрипт 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%+
2025-08-27 17:51:59 +04:00

2999 lines
121 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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 &amp;&amp; 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)
&lt;-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 &amp;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(&amp;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(), &amp;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(&amp;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(), &amp;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 &amp;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(&amp;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, &amp;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(&amp;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, &amp;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 &amp;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(&amp;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, &amp;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(&amp;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, &amp;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 &amp;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(&amp;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, &amp;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(&amp;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(&amp;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(&amp;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 &lt;= 0 </span><span class="cov0" title="0">{
req.Page = 1
}</span>
<span class="cov0" title="0">if req.PageSize &lt;= 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, &amp;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 &amp;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 &lt;token&gt;"
<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 &amp;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 := &amp;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 &amp;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, &amp;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 &amp;&amp; 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 &amp;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(&amp;logrus.JSONFormatter{})
return &amp;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 &amp;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 := &amp;models.Item{}
err := r.db.QueryRowContext(ctx, query, id, orgID).Scan(
&amp;item.ID,
&amp;item.OrganizationID,
&amp;item.Name,
&amp;item.Description,
&amp;item.Category,
&amp;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 := &amp;models.Item{}
err := rows.Scan(
&amp;item.ID,
&amp;item.OrganizationID,
&amp;item.Name,
&amp;item.Description,
&amp;item.Category,
&amp;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 := &amp;models.Item{}
err := rows.Scan(
&amp;item.ID,
&amp;item.OrganizationID,
&amp;item.Name,
&amp;item.Description,
&amp;item.Category,
&amp;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 &amp;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 := &amp;models.StorageLocation{}
err := r.db.QueryRowContext(ctx, query, id, orgID).Scan(
&amp;location.ID,
&amp;location.OrganizationID,
&amp;location.ParentID,
&amp;location.Name,
&amp;location.Address,
&amp;location.Type,
&amp;coordinatesJSON,
&amp;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) &gt; 0 </span><span class="cov8" title="1">{
err = json.Unmarshal(coordinatesJSON, &amp;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 := &amp;models.StorageLocation{}
err := rows.Scan(
&amp;location.ID,
&amp;location.OrganizationID,
&amp;location.ParentID,
&amp;location.Name,
&amp;location.Address,
&amp;location.Type,
&amp;coordinatesJSON,
&amp;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) &gt; 0 </span><span class="cov0" title="0">{
err = json.Unmarshal(coordinatesJSON, &amp;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 := &amp;models.StorageLocation{}
err := rows.Scan(
&amp;location.ID,
&amp;location.OrganizationID,
&amp;location.ParentID,
&amp;location.Name,
&amp;location.Address,
&amp;location.Type,
&amp;coordinatesJSON,
&amp;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) &gt; 0 </span><span class="cov0" title="0">{
err = json.Unmarshal(coordinatesJSON, &amp;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 &amp;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 := &amp;models.ItemPlacement{}
err := rows.Scan(
&amp;placement.ID,
&amp;placement.OrganizationID,
&amp;placement.ItemID,
&amp;placement.LocationID,
&amp;placement.Quantity,
&amp;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 := &amp;models.ItemPlacement{}
err := rows.Scan(
&amp;placement.ID,
&amp;placement.OrganizationID,
&amp;placement.ItemID,
&amp;placement.LocationID,
&amp;placement.Quantity,
&amp;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 := &amp;models.ItemPlacement{}
err := r.db.QueryRowContext(ctx, query, id, orgID).Scan(
&amp;placement.ID,
&amp;placement.OrganizationID,
&amp;placement.ItemID,
&amp;placement.LocationID,
&amp;placement.Quantity,
&amp;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 := &amp;models.ItemWithLocation{}
err := rows.Scan(
&amp;itemWithLocation.Item.ID,
&amp;itemWithLocation.Item.OrganizationID,
&amp;itemWithLocation.Item.Name,
&amp;itemWithLocation.Item.Description,
&amp;itemWithLocation.Item.Category,
&amp;itemWithLocation.Item.CreatedAt,
&amp;itemWithLocation.Location.ID,
&amp;itemWithLocation.Location.OrganizationID,
&amp;itemWithLocation.Location.ParentID,
&amp;itemWithLocation.Location.Name,
&amp;itemWithLocation.Location.Address,
&amp;itemWithLocation.Location.Type,
&amp;coordinatesJSON,
&amp;itemWithLocation.Location.CreatedAt,
&amp;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) &gt; 0 </span><span class="cov8" title="1">{
err = json.Unmarshal(coordinatesJSON, &amp;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 &amp;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 := &amp;models.Organization{}
err := r.db.QueryRowContext(ctx, query, id).Scan(
&amp;org.ID,
&amp;org.Name,
&amp;org.Type,
&amp;settingsJSON,
&amp;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) &gt; 0 </span><span class="cov8" title="1">{
err = json.Unmarshal(settingsJSON, &amp;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 &amp;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 := &amp;models.User{}
err := r.db.QueryRowContext(ctx, query, email).Scan(
&amp;user.ID,
&amp;user.OrganizationID,
&amp;user.Email,
&amp;user.PasswordHash,
&amp;user.Role,
&amp;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 := &amp;models.User{}
err := r.db.QueryRowContext(ctx, query, id).Scan(
&amp;user.ID,
&amp;user.OrganizationID,
&amp;user.Email,
&amp;user.PasswordHash,
&amp;user.Role,
&amp;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 &amp;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 &amp;&amp; existingUser != nil </span><span class="cov0" title="0">{
s.logger.Error("User with this email already exists")
return nil, &amp;ValidationError{Message: "User with this email already exists"}
}</span>
// Создаем организацию
<span class="cov0" title="0">orgID := uuid.New()
org := &amp;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 := &amp;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 &amp;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, &amp;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, &amp;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 &amp;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 &amp;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 := &amp;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 &amp;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 := &amp;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 &amp;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 := &amp;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 := &amp;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>