package handlers import ( "net/http" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/shopspring/decimal" "go.uber.org/zap" "rmser/internal/services/drafts" "rmser/pkg/logger" ) type DraftsHandler struct { service *drafts.Service } func NewDraftsHandler(service *drafts.Service) *DraftsHandler { return &DraftsHandler{service: service} } // GetDraft возвращает полные данные черновика func (h *DraftsHandler) GetDraft(c *gin.Context) { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } draft, err := h.service.GetDraft(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "draft not found"}) return } c.JSON(http.StatusOK, draft) } // GetStores возвращает список складов func (h *DraftsHandler) GetStores(c *gin.Context) { stores, err := h.service.GetActiveStores() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, stores) } // UpdateItemDTO - тело запроса на изменение строки type UpdateItemDTO struct { ProductID *string `json:"product_id"` ContainerID *string `json:"container_id"` Quantity float64 `json:"quantity"` Price float64 `json:"price"` } func (h *DraftsHandler) UpdateItem(c *gin.Context) { draftID, _ := uuid.Parse(c.Param("id")) itemID, _ := uuid.Parse(c.Param("itemId")) var req UpdateItemDTO if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var pID *uuid.UUID if req.ProductID != nil && *req.ProductID != "" { if uid, err := uuid.Parse(*req.ProductID); err == nil { pID = &uid } } var cID *uuid.UUID if req.ContainerID != nil && *req.ContainerID != "" { if uid, err := uuid.Parse(*req.ContainerID); err == nil { cID = &uid } } qty := decimal.NewFromFloat(req.Quantity) price := decimal.NewFromFloat(req.Price) if err := h.service.UpdateItem(draftID, itemID, pID, cID, qty, price); err != nil { logger.Log.Error("Failed to update item", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "updated"}) } type CommitRequestDTO struct { DateIncoming string `json:"date_incoming"` // YYYY-MM-DD StoreID string `json:"store_id"` SupplierID string `json:"supplier_id"` Comment string `json:"comment"` } // CommitDraft сохраняет шапку и отправляет в RMS func (h *DraftsHandler) CommitDraft(c *gin.Context) { draftID, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid draft id"}) return } var req CommitRequestDTO if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Парсинг данных шапки date, err := time.Parse("2006-01-02", req.DateIncoming) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid date format (YYYY-MM-DD)"}) return } storeID, err := uuid.Parse(req.StoreID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid store id"}) return } supplierID, err := uuid.Parse(req.SupplierID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid supplier id"}) return } // 1. Обновляем шапку if err := h.service.UpdateDraftHeader(draftID, &storeID, &supplierID, date, req.Comment); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update header: " + err.Error()}) return } // 2. Отправляем docNum, err := h.service.CommitDraft(draftID) if err != nil { logger.Log.Error("Commit failed", zap.Error(err)) c.JSON(http.StatusBadGateway, gin.H{"error": "RMS error: " + err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "status": "completed", "document_number": docNum, }) } // AddContainerRequestDTO - запрос на создание фасовки type AddContainerRequestDTO struct { ProductID string `json:"product_id" binding:"required"` Name string `json:"name" binding:"required"` Count float64 `json:"count" binding:"required,gt=0"` } // AddContainer создает новую фасовку для товара func (h *DraftsHandler) AddContainer(c *gin.Context) { var req AddContainerRequestDTO if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } pID, err := uuid.Parse(req.ProductID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid product_id"}) return } // Конвертация float64 -> decimal countDec := decimal.NewFromFloat(req.Count) // Вызов сервиса newID, err := h.service.CreateProductContainer(pID, req.Name, countDec) if err != nil { logger.Log.Error("Failed to create container", zap.Error(err)) // Можно возвращать 502, если ошибка от RMS c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "status": "created", "container_id": newID.String(), }) } // DraftListItemDTO - структура элемента списка type DraftListItemDTO struct { ID string `json:"id"` DocumentNumber string `json:"document_number"` DateIncoming string `json:"date_incoming"` // YYYY-MM-DD Status string `json:"status"` ItemsCount int `json:"items_count"` TotalSum float64 `json:"total_sum"` StoreName string `json:"store_name"` CreatedAt string `json:"created_at"` } // GetDrafts возвращает список активных черновиков func (h *DraftsHandler) GetDrafts(c *gin.Context) { list, err := h.service.GetActiveDrafts() if err != nil { logger.Log.Error("Failed to fetch drafts", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } response := make([]DraftListItemDTO, 0, len(list)) for _, d := range list { // Расчет суммы var totalSum decimal.Decimal for _, item := range d.Items { // Если item.Sum посчитана - берем её, иначе (qty * price) if !item.Sum.IsZero() { totalSum = totalSum.Add(item.Sum) } else { totalSum = totalSum.Add(item.Quantity.Mul(item.Price)) } } sumFloat, _ := totalSum.Float64() // Форматирование даты dateStr := "" if d.DateIncoming != nil { dateStr = d.DateIncoming.Format("2006-01-02") } // Имя склада storeName := "" if d.Store != nil { storeName = d.Store.Name } response = append(response, DraftListItemDTO{ ID: d.ID.String(), DocumentNumber: d.DocumentNumber, DateIncoming: dateStr, Status: d.Status, ItemsCount: len(d.Items), TotalSum: sumFloat, StoreName: storeName, CreatedAt: d.CreatedAt.Format(time.RFC3339), }) } c.JSON(http.StatusOK, response) } // DeleteDraft обрабатывает запрос на удаление/отмену func (h *DraftsHandler) DeleteDraft(c *gin.Context) { idStr := c.Param("id") id, err := uuid.Parse(idStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } newStatus, err := h.service.DeleteDraft(id) if err != nil { logger.Log.Error("Failed to delete draft", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Возвращаем новый статус, чтобы фронтенд знал, удалился он совсем или стал CANCELED c.JSON(http.StatusOK, gin.H{ "status": newStatus, "id": id.String(), }) }