Улучшения управления ценами и конфигурациями

- Добавлено отображение последней полученной цены в окне настройки цены
- Добавлен функционал переименования конфигураций (PATCH /api/configs/:uuid/rename)
- Изменён формат имени файла при экспорте: "YYYY-MM-DD NAME SPEC.ext"
- Исправлена сортировка компонентов: перенесена на сервер для корректной работы с пагинацией
- Добавлен расчёт popularity_score на основе котировок из lot_log
- Исправлена потеря настроек (метод, период, коэффициент) при пересчёте цен

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-01-27 11:39:12 +03:00
parent d7d6e9d62c
commit 7ded78f2c3
9 changed files with 269 additions and 73 deletions

View File

@@ -62,8 +62,10 @@ func (h *PricingHandler) ListComponents(c *gin.Context) {
perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "20"))
filter := repository.ComponentFilter{
Category: c.Query("category"),
Search: c.Query("search"),
Category: c.Query("category"),
Search: c.Query("search"),
SortField: c.Query("sort"),
SortDir: c.Query("dir"),
}
if page < 1 {
@@ -203,7 +205,6 @@ func (h *PricingHandler) recalculateSinglePrice(lotName string) {
}
periodDays := comp.PricePeriodDays
usedAllTime := false
var result struct {
Price *float64
}
@@ -213,13 +214,10 @@ func (h *PricingHandler) recalculateSinglePrice(lotName string) {
query := `SELECT AVG(price) as price FROM lot_log WHERE lot = ? AND date >= DATE_SUB(NOW(), INTERVAL ? DAY)`
h.db.Raw(query, lotName, periodDays).Scan(&result)
// If no prices found in period, fall back to all time
// If no prices found in period, fall back to all time but keep user's period setting
if result.Price == nil || *result.Price <= 0 {
query = `SELECT AVG(price) as price FROM lot_log WHERE lot = ?`
h.db.Raw(query, lotName).Scan(&result)
if result.Price != nil && *result.Price > 0 {
usedAllTime = true
}
}
} else {
query := `SELECT AVG(price) as price FROM lot_log WHERE lot = ?`
@@ -236,19 +234,13 @@ func (h *PricingHandler) recalculateSinglePrice(lotName string) {
}
now := time.Now()
updates := map[string]interface{}{
"current_price": finalPrice,
"price_updated_at": now,
}
// If we fell back to all time, update the period setting
if usedAllTime {
updates["price_period_days"] = 0
}
// Only update price, preserve all user settings
h.db.Model(&models.LotMetadata{}).
Where("lot_name = ?", lotName).
Updates(updates)
Updates(map[string]interface{}{
"current_price": finalPrice,
"price_updated_at": now,
})
}
func (h *PricingHandler) RecalculateAll(c *gin.Context) {
@@ -339,22 +331,20 @@ func (h *PricingHandler) RecalculateAll(c *gin.Context) {
continue
}
// Determine which price to use
// Determine which price to use based on component settings
var finalPrice float64
usedAllTime := false
periodDays := comp.PricePeriodDays
if periodDays <= 0 {
// Already using all time
// Use all time
finalPrice = *priceData.AvgPrice
} else {
// Try period price first (using 90-day as proxy)
if priceData.AvgPrice90 != nil && *priceData.AvgPrice90 > 0 {
finalPrice = *priceData.AvgPrice90
} else {
// Fall back to all time
// Fall back to all time if no data in period, but keep user's period setting
finalPrice = *priceData.AvgPrice
usedAllTime = true
}
}
@@ -363,15 +353,12 @@ func (h *PricingHandler) RecalculateAll(c *gin.Context) {
finalPrice = finalPrice * (1 + comp.PriceCoefficient/100)
}
// Only update current_price and price_updated_at, preserve all other settings
updates := map[string]interface{}{
"current_price": finalPrice,
"price_updated_at": now,
}
if usedAllTime {
updates["price_period_days"] = 0
}
err := tx.Model(&models.LotMetadata{}).
Where("lot_name = ?", comp.LotName).
Updates(updates).Error
@@ -400,6 +387,9 @@ func (h *PricingHandler) RecalculateAll(c *gin.Context) {
c.Writer.Flush()
}
// Update popularity scores
h.statsRepo.UpdatePopularityScores()
// Send completion
c.SSEvent("progress", gin.H{
"current": updated + skipped + manual + errors,
@@ -514,6 +504,13 @@ func (h *PricingHandler) PreviewPrice(c *gin.Context) {
var quoteCount int64
h.db.Model(&models.LotLog{}).Where("lot = ?", req.LotName).Count(&quoteCount)
// Get last received price
var lastPrice struct {
Price *float64
Date *time.Time
}
h.db.Raw(`SELECT price, date FROM lot_log WHERE lot = ? ORDER BY date DESC, lot_log_id DESC LIMIT 1`, req.LotName).Scan(&lastPrice)
// Calculate new price based on parameters
var basePrice *float64
if req.PeriodDays > 0 {
@@ -542,5 +539,7 @@ func (h *PricingHandler) PreviewPrice(c *gin.Context) {
"new_price": newPrice,
"quote_count": quoteCount,
"manual_price": comp.ManualPrice,
"last_price": lastPrice.Price,
"last_price_date": lastPrice.Date,
})
}