321 lines
8.0 KiB
Go
321 lines
8.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"git.mchus.pro/mchus/quoteforge/internal/localdb"
|
|
"git.mchus.pro/mchus/quoteforge/internal/services/pricelist"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type PricelistHandler struct {
|
|
service *pricelist.Service
|
|
localDB *localdb.LocalDB
|
|
}
|
|
|
|
func NewPricelistHandler(service *pricelist.Service, localDB *localdb.LocalDB) *PricelistHandler {
|
|
return &PricelistHandler{service: service, localDB: localDB}
|
|
}
|
|
|
|
// List returns all pricelists with pagination
|
|
func (h *PricelistHandler) List(c *gin.Context) {
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "20"))
|
|
activeOnly := c.DefaultQuery("active_only", "false") == "true"
|
|
|
|
var (
|
|
pricelists any
|
|
total int64
|
|
err error
|
|
)
|
|
|
|
if activeOnly {
|
|
pricelists, total, err = h.service.ListActive(page, perPage)
|
|
} else {
|
|
pricelists, total, err = h.service.List(page, perPage)
|
|
}
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// If offline (empty list), fallback to local pricelists
|
|
if total == 0 && h.localDB != nil {
|
|
localPLs, err := h.localDB.GetLocalPricelists()
|
|
if err == nil && len(localPLs) > 0 {
|
|
// Convert to PricelistSummary format
|
|
summaries := make([]map[string]interface{}, len(localPLs))
|
|
for i, lpl := range localPLs {
|
|
summaries[i] = map[string]interface{}{
|
|
"id": lpl.ServerID,
|
|
"version": lpl.Version,
|
|
"created_by": "sync",
|
|
"item_count": 0, // Not tracked
|
|
"usage_count": 0, // Not tracked in local
|
|
"is_active": true,
|
|
"created_at": lpl.CreatedAt,
|
|
"synced_from": "local",
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"pricelists": summaries,
|
|
"total": len(summaries),
|
|
"page": page,
|
|
"per_page": perPage,
|
|
"offline": true,
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"pricelists": pricelists,
|
|
"total": total,
|
|
"page": page,
|
|
"per_page": perPage,
|
|
})
|
|
}
|
|
|
|
// Get returns a single pricelist by ID
|
|
func (h *PricelistHandler) Get(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.ParseUint(idStr, 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid pricelist ID"})
|
|
return
|
|
}
|
|
|
|
pl, err := h.service.GetByID(uint(id))
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "pricelist not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, pl)
|
|
}
|
|
|
|
// Create creates a new pricelist from current prices
|
|
func (h *PricelistHandler) Create(c *gin.Context) {
|
|
canWrite, debugInfo := h.service.CanWriteDebug()
|
|
if !canWrite {
|
|
c.JSON(http.StatusForbidden, gin.H{
|
|
"error": "pricelist write is not allowed",
|
|
"debug": debugInfo,
|
|
})
|
|
return
|
|
}
|
|
|
|
// Get the database username as the creator
|
|
createdBy := h.localDB.GetDBUser()
|
|
if createdBy == "" {
|
|
createdBy = "unknown"
|
|
}
|
|
|
|
pl, err := h.service.CreateFromCurrentPrices(createdBy)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, pl)
|
|
}
|
|
|
|
// CreateWithProgress creates a pricelist and streams progress updates over SSE.
|
|
func (h *PricelistHandler) CreateWithProgress(c *gin.Context) {
|
|
canWrite, debugInfo := h.service.CanWriteDebug()
|
|
if !canWrite {
|
|
c.JSON(http.StatusForbidden, gin.H{
|
|
"error": "pricelist write is not allowed",
|
|
"debug": debugInfo,
|
|
})
|
|
return
|
|
}
|
|
|
|
createdBy := h.localDB.GetDBUser()
|
|
if createdBy == "" {
|
|
createdBy = "unknown"
|
|
}
|
|
|
|
c.Header("Content-Type", "text/event-stream")
|
|
c.Header("Cache-Control", "no-cache")
|
|
c.Header("Connection", "keep-alive")
|
|
c.Header("X-Accel-Buffering", "no")
|
|
|
|
flusher, ok := c.Writer.(http.Flusher)
|
|
if !ok {
|
|
pl, err := h.service.CreateFromCurrentPrices(createdBy)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, pl)
|
|
return
|
|
}
|
|
|
|
sendProgress := func(payload gin.H) {
|
|
c.SSEvent("progress", payload)
|
|
flusher.Flush()
|
|
}
|
|
|
|
sendProgress(gin.H{"current": 0, "total": 4, "status": "starting", "message": "Запуск..."})
|
|
pl, err := h.service.CreateFromCurrentPricesWithProgress(createdBy, func(p pricelist.CreateProgress) {
|
|
sendProgress(gin.H{
|
|
"current": p.Current,
|
|
"total": p.Total,
|
|
"status": p.Status,
|
|
"message": p.Message,
|
|
"updated": p.Updated,
|
|
"errors": p.Errors,
|
|
"lot_name": p.LotName,
|
|
})
|
|
})
|
|
if err != nil {
|
|
sendProgress(gin.H{
|
|
"current": 0,
|
|
"total": 4,
|
|
"status": "error",
|
|
"message": fmt.Sprintf("Ошибка: %v", err),
|
|
})
|
|
return
|
|
}
|
|
|
|
sendProgress(gin.H{
|
|
"current": 4,
|
|
"total": 4,
|
|
"status": "completed",
|
|
"message": "Готово",
|
|
"pricelist": pl,
|
|
})
|
|
}
|
|
|
|
// Delete deletes a pricelist by ID
|
|
func (h *PricelistHandler) Delete(c *gin.Context) {
|
|
canWrite, debugInfo := h.service.CanWriteDebug()
|
|
if !canWrite {
|
|
c.JSON(http.StatusForbidden, gin.H{
|
|
"error": "pricelist write is not allowed",
|
|
"debug": debugInfo,
|
|
})
|
|
return
|
|
}
|
|
|
|
idStr := c.Param("id")
|
|
id, err := strconv.ParseUint(idStr, 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid pricelist ID"})
|
|
return
|
|
}
|
|
|
|
if err := h.service.Delete(uint(id)); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "pricelist deleted"})
|
|
}
|
|
|
|
// SetActive toggles active flag on a pricelist.
|
|
func (h *PricelistHandler) SetActive(c *gin.Context) {
|
|
canWrite, debugInfo := h.service.CanWriteDebug()
|
|
if !canWrite {
|
|
c.JSON(http.StatusForbidden, gin.H{
|
|
"error": "pricelist write is not allowed",
|
|
"debug": debugInfo,
|
|
})
|
|
return
|
|
}
|
|
|
|
idStr := c.Param("id")
|
|
id, err := strconv.ParseUint(idStr, 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid pricelist ID"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
IsActive bool `json:"is_active"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := h.service.SetActive(uint(id), req.IsActive); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "updated", "is_active": req.IsActive})
|
|
}
|
|
|
|
// GetItems returns items for a pricelist with pagination
|
|
func (h *PricelistHandler) GetItems(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.ParseUint(idStr, 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid pricelist ID"})
|
|
return
|
|
}
|
|
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "50"))
|
|
search := c.Query("search")
|
|
|
|
items, total, err := h.service.GetItems(uint(id), page, perPage, search)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"items": items,
|
|
"total": total,
|
|
"page": page,
|
|
"per_page": perPage,
|
|
})
|
|
}
|
|
|
|
// CanWrite returns whether the current user can create pricelists
|
|
func (h *PricelistHandler) CanWrite(c *gin.Context) {
|
|
canWrite, debugInfo := h.service.CanWriteDebug()
|
|
c.JSON(http.StatusOK, gin.H{"can_write": canWrite, "debug": debugInfo})
|
|
}
|
|
|
|
// GetLatest returns the most recent active pricelist
|
|
func (h *PricelistHandler) GetLatest(c *gin.Context) {
|
|
// Try to get from server first
|
|
pl, err := h.service.GetLatestActive()
|
|
if err != nil {
|
|
// If offline or no server pricelists, try to get from local cache
|
|
if h.localDB == nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "no database available"})
|
|
return
|
|
}
|
|
localPL, localErr := h.localDB.GetLatestLocalPricelist()
|
|
if localErr != nil {
|
|
// No local pricelists either
|
|
c.JSON(http.StatusNotFound, gin.H{
|
|
"error": "no pricelists available",
|
|
"local_error": localErr.Error(),
|
|
})
|
|
return
|
|
}
|
|
// Return local pricelist
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"id": localPL.ServerID,
|
|
"version": localPL.Version,
|
|
"created_by": "sync",
|
|
"item_count": 0, // Not tracked in local pricelists
|
|
"is_active": true,
|
|
"created_at": localPL.CreatedAt,
|
|
"synced_from": "local",
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, pl)
|
|
}
|