- Migration 029: local_partnumber_books, local_partnumber_book_items, vendor_spec TEXT column on local_configurations - Models: LocalPartnumberBook, LocalPartnumberBookItem, VendorSpec, VendorSpecItem with JSON Valuer/Scanner - Repository: PartnumberBookRepository (GetActiveBook, FindLotByPartnumber, SaveBook/Items, ListBooks, CountBookItems) - Service: VendorSpecResolver 3-step resolution (book → manual suggestion → unresolved) + AggregateLOTs with is_primary_pn qty logic - Sync: PullPartnumberBooks append-only pull from qt_partnumber_books - Handlers: VendorSpecHandler (GET/PUT/resolve/apply), PartnumberBooksHandler - Routes: /api/configs/:uuid/vendor-spec*, /api/partnumber-books, /api/sync/partnumber-books, /partnumber-books page - UI: 3 top-level tabs [Estimate][BOM вендора][Ценообразование]; Excel paste, PN resolution, inline LOT autocomplete, pricing table - Bible: 03-database.md updated, 09-vendor-spec.md added Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
91 lines
2.4 KiB
Go
91 lines
2.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"git.mchus.pro/mchus/quoteforge/internal/localdb"
|
|
"git.mchus.pro/mchus/quoteforge/internal/repository"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// PartnumberBooksHandler provides read-only access to local partnumber book snapshots.
|
|
type PartnumberBooksHandler struct {
|
|
localDB *localdb.LocalDB
|
|
}
|
|
|
|
func NewPartnumberBooksHandler(localDB *localdb.LocalDB) *PartnumberBooksHandler {
|
|
return &PartnumberBooksHandler{localDB: localDB}
|
|
}
|
|
|
|
// List returns all local partnumber book snapshots.
|
|
// GET /api/partnumber-books
|
|
func (h *PartnumberBooksHandler) List(c *gin.Context) {
|
|
bookRepo := repository.NewPartnumberBookRepository(h.localDB.DB())
|
|
books, err := bookRepo.ListBooks()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
type bookSummary struct {
|
|
ID uint `json:"id"`
|
|
ServerID int `json:"server_id"`
|
|
Version string `json:"version"`
|
|
CreatedAt string `json:"created_at"`
|
|
IsActive bool `json:"is_active"`
|
|
ItemCount int64 `json:"item_count"`
|
|
}
|
|
|
|
summaries := make([]bookSummary, 0, len(books))
|
|
for _, b := range books {
|
|
summaries = append(summaries, bookSummary{
|
|
ID: b.ID,
|
|
ServerID: b.ServerID,
|
|
Version: b.Version,
|
|
CreatedAt: b.CreatedAt.Format("2006-01-02"),
|
|
IsActive: b.IsActive,
|
|
ItemCount: bookRepo.CountBookItems(b.ID),
|
|
})
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"books": summaries,
|
|
"total": len(summaries),
|
|
})
|
|
}
|
|
|
|
// GetItems returns items for a partnumber book by server ID.
|
|
// GET /api/partnumber-books/:id
|
|
func (h *PartnumberBooksHandler) GetItems(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.ParseUint(idStr, 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid book ID"})
|
|
return
|
|
}
|
|
|
|
bookRepo := repository.NewPartnumberBookRepository(h.localDB.DB())
|
|
|
|
// Find local book by server_id
|
|
var book localdb.LocalPartnumberBook
|
|
if err := h.localDB.DB().Where("server_id = ?", id).First(&book).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "partnumber book not found"})
|
|
return
|
|
}
|
|
|
|
items, err := bookRepo.GetBookItems(book.ID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"book_id": book.ServerID,
|
|
"version": book.Version,
|
|
"is_active": book.IsActive,
|
|
"items": items,
|
|
"total": len(items),
|
|
})
|
|
}
|