refactor lot matching into shared module
This commit is contained in:
@@ -5,7 +5,10 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.mchus.pro/mchus/quoteforge/internal/localdb"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/models"
|
||||
@@ -22,78 +25,159 @@ func NewPricelistHandler(service *pricelist.Service, localDB *localdb.LocalDB) *
|
||||
return &PricelistHandler{service: service, localDB: localDB}
|
||||
}
|
||||
|
||||
// refreshLocalPricelistCacheFromServer rehydrates local metadata + items for one server pricelist.
|
||||
func (h *PricelistHandler) refreshLocalPricelistCacheFromServer(serverID uint, onProgress func(synced, total int, message string)) error {
|
||||
if h.localDB == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
report := func(synced, total int, message string) {
|
||||
if onProgress != nil {
|
||||
onProgress(synced, total, message)
|
||||
}
|
||||
}
|
||||
report(0, 0, "Подготовка локального кэша")
|
||||
|
||||
pl, err := h.service.GetByID(serverID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if existing, err := h.localDB.GetLocalPricelistByServerID(serverID); err == nil {
|
||||
if err := h.localDB.DeleteLocalPricelist(existing.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
localPL := &localdb.LocalPricelist{
|
||||
ServerID: pl.ID,
|
||||
Source: pl.Source,
|
||||
Version: pl.Version,
|
||||
Name: pl.Notification,
|
||||
CreatedAt: pl.CreatedAt,
|
||||
SyncedAt: time.Now(),
|
||||
IsUsed: false,
|
||||
}
|
||||
if err := h.localDB.SaveLocalPricelist(localPL); err != nil {
|
||||
return err
|
||||
}
|
||||
report(0, 0, "Локальный кэш обновлён")
|
||||
// Ensure we use persisted local row id (upsert path may not populate struct ID reliably).
|
||||
persistedLocalPL, err := h.localDB.GetLocalPricelistByServerID(serverID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if persistedLocalPL.ID == 0 {
|
||||
return fmt.Errorf("local pricelist id is zero after save (server_id=%d)", serverID)
|
||||
}
|
||||
|
||||
const perPage = 2000
|
||||
synced := 0
|
||||
totalItems := 0
|
||||
gotTotal := false
|
||||
for page := 1; ; page++ {
|
||||
items, total, err := h.service.GetItems(serverID, page, perPage, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !gotTotal {
|
||||
totalItems = int(total)
|
||||
gotTotal = true
|
||||
}
|
||||
if len(items) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
localItems := make([]localdb.LocalPricelistItem, 0, len(items))
|
||||
for _, item := range items {
|
||||
partnumbers := make(localdb.LocalStringList, 0, len(item.Partnumbers))
|
||||
partnumbers = append(partnumbers, item.Partnumbers...)
|
||||
localItems = append(localItems, localdb.LocalPricelistItem{
|
||||
PricelistID: persistedLocalPL.ID,
|
||||
LotName: item.LotName,
|
||||
Price: item.Price,
|
||||
AvailableQty: item.AvailableQty,
|
||||
Partnumbers: partnumbers,
|
||||
})
|
||||
}
|
||||
if err := h.localDB.SaveLocalPricelistItems(localItems); err != nil {
|
||||
return err
|
||||
}
|
||||
synced += len(localItems)
|
||||
report(synced, totalItems, "Синхронизация позиций в локальный кэш")
|
||||
|
||||
if int64(page*perPage) >= total {
|
||||
break
|
||||
}
|
||||
}
|
||||
report(synced, totalItems, "Локальный кэш синхронизирован")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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"
|
||||
source := c.Query("source")
|
||||
|
||||
var (
|
||||
pricelists any
|
||||
total int64
|
||||
err error
|
||||
)
|
||||
|
||||
if activeOnly {
|
||||
pricelists, total, err = h.service.ListActiveBySource(page, perPage, source)
|
||||
} else {
|
||||
pricelists, total, err = h.service.ListBySource(page, perPage, source)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if perPage < 1 {
|
||||
perPage = 20
|
||||
}
|
||||
source := c.Query("source")
|
||||
activeOnly := c.DefaultQuery("active_only", "false") == "true"
|
||||
|
||||
localPLs, err := h.localDB.GetLocalPricelists()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
isOffline := false
|
||||
if v, ok := c.Get("is_offline"); ok {
|
||||
if b, ok := v.(bool); ok {
|
||||
isOffline = b
|
||||
if source != "" {
|
||||
filtered := localPLs[:0]
|
||||
for _, lpl := range localPLs {
|
||||
if strings.EqualFold(lpl.Source, source) {
|
||||
filtered = append(filtered, lpl)
|
||||
}
|
||||
}
|
||||
localPLs = filtered
|
||||
}
|
||||
|
||||
// Fallback to local pricelists only in explicit offline mode.
|
||||
if isOffline && total == 0 && h.localDB != nil {
|
||||
localPLs, err := h.localDB.GetLocalPricelists()
|
||||
if err == nil && len(localPLs) > 0 {
|
||||
if source != "" {
|
||||
filtered := localPLs[:0]
|
||||
for _, lpl := range localPLs {
|
||||
if lpl.Source == source {
|
||||
filtered = append(filtered, lpl)
|
||||
}
|
||||
}
|
||||
localPLs = filtered
|
||||
}
|
||||
// Convert to PricelistSummary format
|
||||
summaries := make([]map[string]interface{}, len(localPLs))
|
||||
for i, lpl := range localPLs {
|
||||
summaries[i] = map[string]interface{}{
|
||||
"id": lpl.ServerID,
|
||||
"source": lpl.Source,
|
||||
"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
|
||||
if activeOnly {
|
||||
// Local cache stores only active snapshots for normal operations.
|
||||
}
|
||||
sort.SliceStable(localPLs, func(i, j int) bool { return localPLs[i].CreatedAt.After(localPLs[j].CreatedAt) })
|
||||
total := len(localPLs)
|
||||
start := (page - 1) * perPage
|
||||
if start > total {
|
||||
start = total
|
||||
}
|
||||
end := start + perPage
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
pageSlice := localPLs[start:end]
|
||||
summaries := make([]map[string]interface{}, 0, len(pageSlice))
|
||||
for _, lpl := range pageSlice {
|
||||
itemCount := h.localDB.CountLocalPricelistItems(lpl.ID)
|
||||
usageCount := 0
|
||||
if lpl.IsUsed {
|
||||
usageCount = 1
|
||||
}
|
||||
summaries = append(summaries, map[string]interface{}{
|
||||
"id": lpl.ServerID,
|
||||
"source": lpl.Source,
|
||||
"version": lpl.Version,
|
||||
"created_by": "sync",
|
||||
"item_count": itemCount,
|
||||
"usage_count": usageCount,
|
||||
"is_active": true,
|
||||
"created_at": lpl.CreatedAt,
|
||||
"synced_from": "local",
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"pricelists": pricelists,
|
||||
"pricelists": summaries,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"per_page": perPage,
|
||||
@@ -109,13 +193,22 @@ func (h *PricelistHandler) Get(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
pl, err := h.service.GetByID(uint(id))
|
||||
localPL, err := h.localDB.GetLocalPricelistByServerID(uint(id))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "pricelist not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, pl)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"id": localPL.ServerID,
|
||||
"source": localPL.Source,
|
||||
"version": localPL.Version,
|
||||
"created_by": "sync",
|
||||
"item_count": h.localDB.CountLocalPricelistItems(localPL.ID),
|
||||
"is_active": true,
|
||||
"created_at": localPL.CreatedAt,
|
||||
"synced_from": "local",
|
||||
})
|
||||
}
|
||||
|
||||
// Create creates a new pricelist from current prices
|
||||
@@ -161,6 +254,14 @@ func (h *PricelistHandler) Create(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Keep local cache consistent for local-first reads (metadata + items).
|
||||
if err := h.refreshLocalPricelistCacheFromServer(pl.ID, nil); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "pricelist created on server but failed to refresh local cache: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, pl)
|
||||
}
|
||||
|
||||
@@ -223,10 +324,19 @@ func (h *PricelistHandler) CreateWithProgress(c *gin.Context) {
|
||||
|
||||
sendProgress(gin.H{"current": 0, "total": 4, "status": "starting", "message": "Запуск..."})
|
||||
pl, err := h.service.CreateForSourceWithProgress(createdBy, source, sourceItems, func(p pricelist.CreateProgress) {
|
||||
// Composite progress: 0-85% server creation, 86-99% local cache sync.
|
||||
current := int(float64(p.Current) * 0.85)
|
||||
if p.Status == "completed" {
|
||||
current = 85
|
||||
}
|
||||
status := p.Status
|
||||
if status == "completed" {
|
||||
status = "server_completed"
|
||||
}
|
||||
sendProgress(gin.H{
|
||||
"current": p.Current,
|
||||
"current": current,
|
||||
"total": p.Total,
|
||||
"status": p.Status,
|
||||
"status": status,
|
||||
"message": p.Message,
|
||||
"updated": p.Updated,
|
||||
"errors": p.Errors,
|
||||
@@ -243,6 +353,34 @@ func (h *PricelistHandler) CreateWithProgress(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.refreshLocalPricelistCacheFromServer(pl.ID, func(synced, total int, message string) {
|
||||
current := 86
|
||||
if total > 0 {
|
||||
progressPart := int(float64(synced) / float64(total) * 13.0) // 86..99
|
||||
if progressPart > 13 {
|
||||
progressPart = 13
|
||||
}
|
||||
current = 86 + progressPart
|
||||
}
|
||||
if current > 99 {
|
||||
current = 99
|
||||
}
|
||||
sendProgress(gin.H{
|
||||
"current": current,
|
||||
"total": 100,
|
||||
"status": "sync_local_cache",
|
||||
"message": message,
|
||||
})
|
||||
}); err != nil {
|
||||
sendProgress(gin.H{
|
||||
"current": 4,
|
||||
"total": 4,
|
||||
"status": "error",
|
||||
"message": fmt.Sprintf("Прайслист создан, но локальный кэш не обновлён: %v", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
sendProgress(gin.H{
|
||||
"current": 4,
|
||||
"total": 4,
|
||||
@@ -275,6 +413,18 @@ func (h *PricelistHandler) Delete(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Local-first UI reads pricelists from SQLite cache. Keep cache in sync right away.
|
||||
if h.localDB != nil {
|
||||
if localPL, err := h.localDB.GetLocalPricelistByServerID(uint(id)); err == nil {
|
||||
if err := h.localDB.DeleteLocalPricelist(localPL.ID); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "pricelist deleted on server but failed to update local cache: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "pricelist deleted"})
|
||||
}
|
||||
|
||||
@@ -309,6 +459,47 @@ func (h *PricelistHandler) SetActive(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Local-first table stores only active snapshots. Reflect toggles immediately.
|
||||
if h.localDB != nil {
|
||||
localPL, err := h.localDB.GetLocalPricelistByServerID(uint(id))
|
||||
if err == nil {
|
||||
if req.IsActive {
|
||||
// Ensure local active row has complete cache (metadata + items).
|
||||
if h.localDB.CountLocalPricelistItems(localPL.ID) == 0 {
|
||||
if err := h.refreshLocalPricelistCacheFromServer(uint(id), nil); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "updated on server but failed to refresh local cache: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
localPL.SyncedAt = time.Now()
|
||||
if saveErr := h.localDB.SaveLocalPricelist(localPL); saveErr != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "updated on server but failed to update local cache: " + saveErr.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Inactive entries should disappear from local active cache list.
|
||||
if delErr := h.localDB.DeleteLocalPricelist(localPL.ID); delErr != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "updated on server but failed to update local cache: " + delErr.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
} else if req.IsActive {
|
||||
if err := h.refreshLocalPricelistCacheFromServer(uint(id), nil); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "updated on server but failed to seed local cache: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "updated", "is_active": req.IsActive})
|
||||
}
|
||||
|
||||
@@ -325,20 +516,52 @@ func (h *PricelistHandler) GetItems(c *gin.Context) {
|
||||
perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "50"))
|
||||
search := c.Query("search")
|
||||
|
||||
items, total, err := h.service.GetItems(uint(id), page, perPage, search)
|
||||
localPL, err := h.localDB.GetLocalPricelistByServerID(uint(id))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "pricelist not found"})
|
||||
return
|
||||
}
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if perPage < 1 {
|
||||
perPage = 50
|
||||
}
|
||||
var items []localdb.LocalPricelistItem
|
||||
dbq := h.localDB.DB().Model(&localdb.LocalPricelistItem{}).Where("pricelist_id = ?", localPL.ID)
|
||||
if strings.TrimSpace(search) != "" {
|
||||
dbq = dbq.Where("lot_name LIKE ?", "%"+strings.TrimSpace(search)+"%")
|
||||
}
|
||||
var total int64
|
||||
if err := dbq.Count(&total).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
pl, _ := h.service.GetByID(uint(id))
|
||||
source := ""
|
||||
if pl != nil {
|
||||
source = pl.Source
|
||||
offset := (page - 1) * perPage
|
||||
|
||||
if err := dbq.Order("lot_name").Offset(offset).Limit(perPage).Find(&items).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
resultItems := make([]gin.H, 0, len(items))
|
||||
for _, item := range items {
|
||||
category := ""
|
||||
if parts := strings.SplitN(item.LotName, "_", 2); len(parts) > 0 {
|
||||
category = parts[0]
|
||||
}
|
||||
resultItems = append(resultItems, gin.H{
|
||||
"id": item.ID,
|
||||
"lot_name": item.LotName,
|
||||
"price": item.Price,
|
||||
"category": category,
|
||||
"available_qty": item.AvailableQty,
|
||||
"partnumbers": []string(item.Partnumbers),
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"source": source,
|
||||
"items": items,
|
||||
"source": localPL.Source,
|
||||
"items": resultItems,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"per_page": perPage,
|
||||
@@ -353,11 +576,21 @@ func (h *PricelistHandler) GetLotNames(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
lotNames, err := h.service.GetLotNames(uint(id))
|
||||
localPL, err := h.localDB.GetLocalPricelistByServerID(uint(id))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "pricelist not found"})
|
||||
return
|
||||
}
|
||||
items, err := h.localDB.GetLocalPricelistItems(localPL.ID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
lotNames := make([]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
lotNames = append(lotNames, item.LotName)
|
||||
}
|
||||
sort.Strings(lotNames)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"lot_names": lotNames,
|
||||
@@ -376,36 +609,19 @@ func (h *PricelistHandler) GetLatest(c *gin.Context) {
|
||||
source := c.DefaultQuery("source", string(models.PricelistSourceEstimate))
|
||||
source = string(models.NormalizePricelistSource(source))
|
||||
|
||||
// Try to get from server first
|
||||
pl, err := h.service.GetLatestActiveBySource(source)
|
||||
localPL, err := h.localDB.GetLatestLocalPricelistBySource(source)
|
||||
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.GetLatestLocalPricelistBySource(source)
|
||||
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,
|
||||
"source": localPL.Source,
|
||||
"version": localPL.Version,
|
||||
"created_by": "sync",
|
||||
"item_count": 0, // Not tracked in local pricelists
|
||||
"is_active": true,
|
||||
"created_at": localPL.CreatedAt,
|
||||
"synced_from": "local",
|
||||
})
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "no pricelists available"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, pl)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"id": localPL.ServerID,
|
||||
"source": localPL.Source,
|
||||
"version": localPL.Version,
|
||||
"created_by": "sync",
|
||||
"item_count": h.localDB.CountLocalPricelistItems(localPL.ID),
|
||||
"is_active": true,
|
||||
"created_at": localPL.CreatedAt,
|
||||
"synced_from": "local",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"git.mchus.pro/mchus/quoteforge/internal/services"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/services/alerts"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/services/pricing"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/warehouse"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -1296,41 +1297,11 @@ func (h *PricingHandler) ListLotsTable(c *gin.Context) {
|
||||
estimateMap[ec.Lot] = ec.Count
|
||||
}
|
||||
|
||||
type stockRow struct {
|
||||
LotName string `gorm:"column:lot_name"`
|
||||
Qty *float64 `gorm:"column:total_qty"`
|
||||
}
|
||||
var stockRows []stockRow
|
||||
if err := h.db.Raw(`
|
||||
SELECT lp.lot_name, SUM(sl.qty) as total_qty
|
||||
FROM stock_log sl
|
||||
INNER JOIN lot_partnumbers lp ON LOWER(TRIM(lp.partnumber)) = LOWER(TRIM(sl.partnumber))
|
||||
INNER JOIN (SELECT MAX(date) as max_date FROM stock_log) md ON sl.date = md.max_date
|
||||
WHERE lp.lot_name IN ?
|
||||
GROUP BY lp.lot_name
|
||||
`, lotNames).Scan(&stockRows).Error; err != nil {
|
||||
stockQtyByLot, pnMap, err := warehouse.LoadLotMetrics(h.db, lotNames, true)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
stockMap := make(map[string]*float64, len(stockRows))
|
||||
for _, sr := range stockRows {
|
||||
qty := sr.Qty
|
||||
stockMap[sr.LotName] = qty
|
||||
}
|
||||
|
||||
type pnRow struct {
|
||||
LotName string `gorm:"column:lot_name"`
|
||||
Partnumber string `gorm:"column:partnumber"`
|
||||
}
|
||||
var pnRows []pnRow
|
||||
if err := h.db.Raw("SELECT lot_name, partnumber FROM lot_partnumbers WHERE lot_name IN ? ORDER BY lot_name, partnumber", lotNames).Scan(&pnRows).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
pnMap := make(map[string][]string, len(pnRows))
|
||||
for _, pn := range pnRows {
|
||||
pnMap[pn.LotName] = append(pnMap[pn.LotName], pn.Partnumber)
|
||||
}
|
||||
|
||||
result := make([]LotTableRow, len(rows))
|
||||
for i, r := range rows {
|
||||
@@ -1349,7 +1320,10 @@ func (h *PricingHandler) ListLotsTable(c *gin.Context) {
|
||||
Partnumbers: pnMap[r.LotName],
|
||||
Popularity: pop,
|
||||
EstimateCount: estimateMap[r.LotName],
|
||||
StockQty: stockMap[r.LotName],
|
||||
}
|
||||
if qty, ok := stockQtyByLot[r.LotName]; ok {
|
||||
q := qty
|
||||
result[i].StockQty = &q
|
||||
}
|
||||
if result[i].Partnumbers == nil {
|
||||
result[i].Partnumbers = []string{}
|
||||
|
||||
Reference in New Issue
Block a user