feat: add pricelist CSV export and improve description display

- Add CSV export functionality for pricelists with download button
- Export includes all pricelist items with proper UTF-8 encoding
- Support both warehouse and estimate pricelist sources
- Remove description column from admin pricing tables
- Show description as tooltip on row hover instead
- Improve table layout by removing redundant column

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 12:25:38 +03:00
parent ed339a172b
commit 9bc01831c9
4 changed files with 212 additions and 14 deletions

View File

@@ -1,7 +1,9 @@
package handlers
import (
"encoding/csv"
"errors"
"fmt"
"io"
"net/http"
"strconv"
@@ -239,3 +241,160 @@ func (h *PricelistHandler) GetLatest(c *gin.Context) {
}
c.JSON(http.StatusOK, pl)
}
func (h *PricelistHandler) ExportCSV(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid pricelist ID"})
return
}
// Get pricelist info
pl, err := h.service.GetByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "pricelist not found"})
return
}
// Get all items (no pagination)
items, _, err := h.service.GetItems(uint(id), 1, 999999, "")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Set response headers for CSV download
filename := fmt.Sprintf("pricelist_%s.csv", pl.Version)
c.Header("Content-Type", "text/csv; charset=utf-8")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
// Create CSV writer
writer := csv.NewWriter(c.Writer)
defer writer.Flush()
// Write UTF-8 BOM for Excel compatibility
c.Writer.Write([]byte{0xEF, 0xBB, 0xBF})
// Determine if warehouse source
isWarehouse := strings.ToLower(pl.Source) == "warehouse"
// Write CSV header
var header []string
if isWarehouse {
header = []string{"Артикул", "Категория", "Описание", "Доступно", "Partnumbers", "Цена, $", "Настройки"}
} else {
header = []string{"Артикул", "Категория", "Описание", "Цена, $", "Настройки"}
}
if err := writer.Write(header); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to write CSV header"})
return
}
// Write items
for _, item := range items {
row := make([]string, 0, len(header))
// Артикул
row = append(row, item.LotName)
// Категория
category := item.Category
if category == "" {
category = "-"
}
row = append(row, category)
// Описание
description := item.LotDescription
if description == "" {
description = "-"
}
row = append(row, description)
if isWarehouse {
// Доступно
qty := "-"
if item.AvailableQty != nil {
qty = fmt.Sprintf("%.3f", *item.AvailableQty)
}
row = append(row, qty)
// Partnumbers
partnumbers := "-"
if len(item.Partnumbers) > 0 {
partnumbers = strings.Join(item.Partnumbers, ", ")
}
row = append(row, partnumbers)
}
// Цена
row = append(row, fmt.Sprintf("%.2f", item.Price))
// Настройки
settings := formatPriceSettings(item)
row = append(row, settings)
if err := writer.Write(row); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to write CSV row"})
return
}
}
}
func formatPriceSettings(item models.PricelistItem) string {
var settings []string
hasManualPrice := item.ManualPrice != nil && *item.ManualPrice > 0
hasMeta := item.MetaPrices != ""
method := strings.ToLower(item.PriceMethod)
// Method indicator
if hasManualPrice {
settings = append(settings, "РУЧН")
} else if method == "average" {
settings = append(settings, "Сред")
} else if method == "weighted_median" {
settings = append(settings, "Взвеш. мед")
} else {
settings = append(settings, "Мед")
}
// Period (only if not manual price)
if !hasManualPrice {
period := item.PricePeriodDays
switch period {
case 7:
settings = append(settings, "1н")
case 30:
settings = append(settings, "1м")
case 90:
settings = append(settings, "3м")
case 365:
settings = append(settings, "1г")
case 0:
settings = append(settings, "все")
default:
settings = append(settings, fmt.Sprintf("%dд", period))
}
}
// Coefficient
if item.PriceCoefficient != 0 {
coef := item.PriceCoefficient
if coef > 0 {
settings = append(settings, fmt.Sprintf("+%.0f%%", coef))
} else {
settings = append(settings, fmt.Sprintf("%.0f%%", coef))
}
}
// Meta article indicator
if hasMeta {
settings = append(settings, "МЕТА")
}
if len(settings) == 0 {
return "-"
}
return strings.Join(settings, " | ")
}