Implement warehouse/lot pricing updates and configurator performance fixes
This commit is contained in:
@@ -997,6 +997,7 @@ func (h *PricingHandler) ImportStockLog(c *gin.Context) {
|
||||
"conflicts": result.Conflicts,
|
||||
"fallback_matches": result.FallbackMatches,
|
||||
"parse_errors": result.ParseErrors,
|
||||
"qty_parse_errors": result.QtyParseErrors,
|
||||
"ignored": result.Ignored,
|
||||
"mapping_suggestions": result.MappingSuggestions,
|
||||
"import_date": result.ImportDate.Format("2006-01-02"),
|
||||
@@ -1031,6 +1032,7 @@ func (h *PricingHandler) ImportStockLog(c *gin.Context) {
|
||||
"conflicts": p.Conflicts,
|
||||
"fallback_matches": p.FallbackMatches,
|
||||
"parse_errors": p.ParseErrors,
|
||||
"qty_parse_errors": p.QtyParseErrors,
|
||||
"ignored": p.Ignored,
|
||||
"mapping_suggestions": p.MappingSuggestions,
|
||||
"import_date": p.ImportDate,
|
||||
@@ -1183,6 +1185,231 @@ func (h *PricingHandler) DeleteStockIgnoreRule(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"deleted": deleted})
|
||||
}
|
||||
|
||||
type LotTableRow struct {
|
||||
LotName string `json:"lot_name"`
|
||||
LotDescription string `json:"lot_description"`
|
||||
Category string `json:"category"`
|
||||
Partnumbers []string `json:"partnumbers"`
|
||||
Popularity float64 `json:"popularity"`
|
||||
EstimateCount int64 `json:"estimate_count"`
|
||||
StockQty *float64 `json:"stock_qty"`
|
||||
}
|
||||
|
||||
func (h *PricingHandler) ListLotsTable(c *gin.Context) {
|
||||
if h.db == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||
"error": "Список LOT доступен только в онлайн режиме",
|
||||
"offline": true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "50"))
|
||||
search := strings.TrimSpace(c.Query("search"))
|
||||
sortFieldParam := c.DefaultQuery("sort", "lot_name")
|
||||
sortDirParam := strings.ToUpper(c.DefaultQuery("dir", "asc"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if perPage < 1 || perPage > 200 {
|
||||
perPage = 50
|
||||
}
|
||||
if sortDirParam != "ASC" && sortDirParam != "DESC" {
|
||||
sortDirParam = "ASC"
|
||||
}
|
||||
|
||||
type lotRow struct {
|
||||
LotName string `gorm:"column:lot_name"`
|
||||
LotDescription string `gorm:"column:lot_description"`
|
||||
CategoryCode *string `gorm:"column:category_code"`
|
||||
Popularity *float64 `gorm:"column:popularity_score"`
|
||||
}
|
||||
|
||||
baseQuery := h.db.Table("lot").
|
||||
Select("lot.lot_name, lot.lot_description, qt_categories.code as category_code, qt_lot_metadata.popularity_score").
|
||||
Joins("LEFT JOIN qt_lot_metadata ON qt_lot_metadata.lot_name = lot.lot_name").
|
||||
Joins("LEFT JOIN qt_categories ON qt_categories.id = qt_lot_metadata.category_id")
|
||||
|
||||
if search != "" {
|
||||
baseQuery = baseQuery.Where("lot.lot_name LIKE ? OR lot.lot_description LIKE ?", "%"+search+"%", "%"+search+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := baseQuery.Count(&total).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
allowedDBSorts := map[string]string{
|
||||
"lot_name": "lot.lot_name",
|
||||
"category": "qt_categories.code",
|
||||
"popularity_score": "qt_lot_metadata.popularity_score",
|
||||
}
|
||||
needsComputedSort := sortFieldParam == "estimate_count" || sortFieldParam == "stock_qty"
|
||||
|
||||
var rows []lotRow
|
||||
rowsQuery := baseQuery.Session(&gorm.Session{})
|
||||
if needsComputedSort {
|
||||
rowsQuery = rowsQuery.Order("lot.lot_name ASC")
|
||||
} else {
|
||||
orderCol, ok := allowedDBSorts[sortFieldParam]
|
||||
if !ok {
|
||||
orderCol = "lot.lot_name"
|
||||
}
|
||||
rowsQuery = rowsQuery.Order(orderCol + " " + sortDirParam).Order("lot.lot_name ASC")
|
||||
rowsQuery = rowsQuery.Offset((page - 1) * perPage).Limit(perPage)
|
||||
}
|
||||
if err := rowsQuery.Find(&rows).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"lots": []LotTableRow{},
|
||||
"total": total,
|
||||
"page": page,
|
||||
"per_page": perPage,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Collect lot names for batch subqueries
|
||||
lotNames := make([]string, len(rows))
|
||||
for i, r := range rows {
|
||||
lotNames[i] = r.LotName
|
||||
}
|
||||
|
||||
type countRow struct {
|
||||
Lot string `gorm:"column:lot"`
|
||||
Count int64 `gorm:"column:cnt"`
|
||||
}
|
||||
var estimateCounts []countRow
|
||||
if err := h.db.Raw("SELECT lot, COUNT(*) as cnt FROM lot_log WHERE lot IN ? GROUP BY lot", lotNames).Scan(&estimateCounts).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
estimateMap := make(map[string]int64, len(estimateCounts))
|
||||
for _, ec := range estimateCounts {
|
||||
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 {
|
||||
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 {
|
||||
cat := ""
|
||||
if r.CategoryCode != nil {
|
||||
cat = *r.CategoryCode
|
||||
}
|
||||
pop := 0.0
|
||||
if r.Popularity != nil {
|
||||
pop = *r.Popularity
|
||||
}
|
||||
result[i] = LotTableRow{
|
||||
LotName: r.LotName,
|
||||
LotDescription: r.LotDescription,
|
||||
Category: cat,
|
||||
Partnumbers: pnMap[r.LotName],
|
||||
Popularity: pop,
|
||||
EstimateCount: estimateMap[r.LotName],
|
||||
StockQty: stockMap[r.LotName],
|
||||
}
|
||||
if result[i].Partnumbers == nil {
|
||||
result[i].Partnumbers = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
if needsComputedSort {
|
||||
sort.SliceStable(result, func(i, j int) bool {
|
||||
if sortFieldParam == "estimate_count" {
|
||||
if result[i].EstimateCount == result[j].EstimateCount {
|
||||
if sortDirParam == "DESC" {
|
||||
return result[i].LotName > result[j].LotName
|
||||
}
|
||||
return result[i].LotName < result[j].LotName
|
||||
}
|
||||
if sortDirParam == "DESC" {
|
||||
return result[i].EstimateCount > result[j].EstimateCount
|
||||
}
|
||||
return result[i].EstimateCount < result[j].EstimateCount
|
||||
}
|
||||
qi := 0.0
|
||||
if result[i].StockQty != nil {
|
||||
qi = *result[i].StockQty
|
||||
}
|
||||
qj := 0.0
|
||||
if result[j].StockQty != nil {
|
||||
qj = *result[j].StockQty
|
||||
}
|
||||
if qi == qj {
|
||||
if sortDirParam == "DESC" {
|
||||
return result[i].LotName > result[j].LotName
|
||||
}
|
||||
return result[i].LotName < result[j].LotName
|
||||
}
|
||||
if sortDirParam == "DESC" {
|
||||
return qi > qj
|
||||
}
|
||||
return qi < qj
|
||||
})
|
||||
|
||||
start := (page - 1) * perPage
|
||||
if start >= len(result) {
|
||||
result = []LotTableRow{}
|
||||
} else {
|
||||
end := start + perPage
|
||||
if end > len(result) {
|
||||
end = len(result)
|
||||
}
|
||||
result = result[start:end]
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"lots": result,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"per_page": perPage,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *PricingHandler) ListLots(c *gin.Context) {
|
||||
if h.db == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||
|
||||
Reference in New Issue
Block a user