feat: сохранять ручные PN→LOT маппинги как lot_suggestion в qt_vendor_partnumber_seen
При сохранении vendor-spec строки с заполненным lot_mappings автоматически
отправляются на сервер и пишутся в новый столбец lot_suggestion. Столбец
хранит JSON-массив [{lot_name, qty}] — тот же формат, что qt_partnumber_book_items.lots_json.
Если миграция ещё не прошла (столбец отсутствует), приложение логирует WARN
и записывает строку без столбца; сбоя нет.
Контракт для инструмента создания partnumber-books описан в bible-local/11-lot-suggestions.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"git.mchus.pro/mchus/quoteforge/internal/localdb"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/repository"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/services"
|
||||
syncsvc "git.mchus.pro/mchus/quoteforge/internal/services/sync"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -16,12 +17,14 @@ import (
|
||||
type VendorSpecHandler struct {
|
||||
localDB *localdb.LocalDB
|
||||
configService *services.LocalConfigurationService
|
||||
syncService *syncsvc.Service // optional; nil = no server push
|
||||
}
|
||||
|
||||
func NewVendorSpecHandler(localDB *localdb.LocalDB) *VendorSpecHandler {
|
||||
func NewVendorSpecHandler(localDB *localdb.LocalDB, syncService *syncsvc.Service) *VendorSpecHandler {
|
||||
return &VendorSpecHandler{
|
||||
localDB: localDB,
|
||||
configService: services.NewLocalConfigurationService(localDB, nil, nil, func() bool { return false }),
|
||||
syncService: syncService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,9 +114,57 @@ func (h *VendorSpecHandler) PutVendorSpec(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Push manual lot mappings as suggestions to the server (best-effort, non-blocking).
|
||||
h.pushLotSuggestions(body.VendorSpec)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"vendor_spec": spec})
|
||||
}
|
||||
|
||||
// pushLotSuggestions sends manual PN→LOT mappings to qt_vendor_partnumber_seen.lot_suggestion.
|
||||
// Errors are logged and silently dropped — they must not affect the HTTP response.
|
||||
func (h *VendorSpecHandler) pushLotSuggestions(spec []localdb.VendorSpecItem) {
|
||||
if h.syncService == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var items []syncsvc.SeenPartnumber
|
||||
for _, row := range spec {
|
||||
if row.VendorPartnumber == "" || len(row.LotMappings) == 0 {
|
||||
continue
|
||||
}
|
||||
suggestion := make([]syncsvc.LotSuggestionEntry, 0, len(row.LotMappings))
|
||||
for _, m := range row.LotMappings {
|
||||
if m.LotName == "" {
|
||||
continue
|
||||
}
|
||||
qty := m.QuantityPerPN
|
||||
if qty < 1 {
|
||||
qty = 1
|
||||
}
|
||||
suggestion = append(suggestion, syncsvc.LotSuggestionEntry{
|
||||
LotName: m.LotName,
|
||||
Qty: qty,
|
||||
})
|
||||
}
|
||||
if len(suggestion) == 0 {
|
||||
continue
|
||||
}
|
||||
items = append(items, syncsvc.SeenPartnumber{
|
||||
Partnumber: row.VendorPartnumber,
|
||||
Description: row.Description,
|
||||
LotSuggestion: suggestion,
|
||||
})
|
||||
}
|
||||
|
||||
if len(items) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.syncService.PushPartnumberSeen(items); err != nil {
|
||||
slog.Warn("vendor_spec: failed to push lot suggestions to server", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeLotMappings(in []localdb.VendorSpecLotMapping) []localdb.VendorSpecLotMapping {
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user