package handler import ( "errors" "net/http" "strconv" "time" "github.com/saveinmed/backend-go/internal/domain" ) // CreateProduct godoc // @Summary Cadastro de produto com rastreabilidade de lote // @Tags Produtos // @Accept json // @Produce json // @Param product body registerProductRequest true "Produto" // @Success 201 {object} domain.Product // @Router /api/v1/products [post] func (h *Handler) CreateProduct(w http.ResponseWriter, r *http.Request) { var req registerProductRequest if err := decodeJSON(r.Context(), r, &req); err != nil { writeError(w, http.StatusBadRequest, err) return } product := &domain.Product{ SellerID: req.SellerID, Name: req.Name, Description: req.Description, Batch: req.Batch, ExpiresAt: req.ExpiresAt, PriceCents: req.PriceCents, Stock: req.Stock, } if err := h.svc.RegisterProduct(r.Context(), product); err != nil { writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusCreated, product) } // ListProducts godoc // @Summary Lista catálogo com lote e validade // @Tags Produtos // @Produce json // @Success 200 {array} domain.Product // @Router /api/v1/products [get] func (h *Handler) ListProducts(w http.ResponseWriter, r *http.Request) { products, err := h.svc.ListProducts(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusOK, products) } // GetProduct godoc // @Summary Obter produto // @Tags Produtos // @Produce json // @Param id path string true "Product ID" // @Success 200 {object} domain.Product // @Failure 404 {object} map[string]string // @Router /api/v1/products/{id} [get] func (h *Handler) GetProduct(w http.ResponseWriter, r *http.Request) { id, err := parseUUIDFromPath(r.URL.Path) if err != nil { writeError(w, http.StatusBadRequest, err) return } product, err := h.svc.GetProduct(r.Context(), id) if err != nil { writeError(w, http.StatusNotFound, err) return } writeJSON(w, http.StatusOK, product) } // UpdateProduct godoc // @Summary Atualizar produto // @Tags Produtos // @Accept json // @Produce json // @Param id path string true "Product ID" // @Param payload body updateProductRequest true "Campos para atualização" // @Success 200 {object} domain.Product // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string // @Router /api/v1/products/{id} [patch] func (h *Handler) UpdateProduct(w http.ResponseWriter, r *http.Request) { id, err := parseUUIDFromPath(r.URL.Path) if err != nil { writeError(w, http.StatusBadRequest, err) return } var req updateProductRequest if err := decodeJSON(r.Context(), r, &req); err != nil { writeError(w, http.StatusBadRequest, err) return } product, err := h.svc.GetProduct(r.Context(), id) if err != nil { writeError(w, http.StatusNotFound, err) return } if req.SellerID != nil { product.SellerID = *req.SellerID } if req.Name != nil { product.Name = *req.Name } if req.Description != nil { product.Description = *req.Description } if req.Batch != nil { product.Batch = *req.Batch } if req.ExpiresAt != nil { product.ExpiresAt = *req.ExpiresAt } if req.PriceCents != nil { product.PriceCents = *req.PriceCents } if req.Stock != nil { product.Stock = *req.Stock } if err := h.svc.UpdateProduct(r.Context(), product); err != nil { writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusOK, product) } // DeleteProduct godoc // @Summary Remover produto // @Tags Produtos // @Param id path string true "Product ID" // @Success 204 "" // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string // @Router /api/v1/products/{id} [delete] func (h *Handler) DeleteProduct(w http.ResponseWriter, r *http.Request) { id, err := parseUUIDFromPath(r.URL.Path) if err != nil { writeError(w, http.StatusBadRequest, err) return } if err := h.svc.DeleteProduct(r.Context(), id); err != nil { writeError(w, http.StatusBadRequest, err) return } w.WriteHeader(http.StatusNoContent) } // ListInventory godoc // @Summary Listar estoque // @Tags Estoque // @Security BearerAuth // @Produce json // @Param expires_in_days query int false "Dias para expiração" // @Success 200 {array} domain.InventoryItem // @Router /api/v1/inventory [get] // ListInventory exposes stock with expiring batch filters. func (h *Handler) ListInventory(w http.ResponseWriter, r *http.Request) { var filter domain.InventoryFilter if days := r.URL.Query().Get("expires_in_days"); days != "" { n, err := strconv.Atoi(days) if err != nil || n < 0 { writeError(w, http.StatusBadRequest, errors.New("invalid expires_in_days")) return } expires := time.Now().Add(time.Duration(n) * 24 * time.Hour) filter.ExpiringBefore = &expires } inventory, err := h.svc.ListInventory(r.Context(), filter) if err != nil { writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusOK, inventory) } // AdjustInventory godoc // @Summary Ajustar estoque // @Tags Estoque // @Security BearerAuth // @Accept json // @Produce json // @Param payload body inventoryAdjustRequest true "Ajuste de estoque" // @Success 200 {object} domain.InventoryItem // @Failure 400 {object} map[string]string // @Router /api/v1/inventory/adjust [post] // AdjustInventory handles manual stock corrections. func (h *Handler) AdjustInventory(w http.ResponseWriter, r *http.Request) { var req inventoryAdjustRequest if err := decodeJSON(r.Context(), r, &req); err != nil { writeError(w, http.StatusBadRequest, err) return } if req.Delta == 0 { writeError(w, http.StatusBadRequest, errors.New("delta must be non-zero")) return } item, err := h.svc.AdjustInventory(r.Context(), req.ProductID, req.Delta, req.Reason) if err != nil { writeError(w, http.StatusBadRequest, err) return } writeJSON(w, http.StatusOK, item) }