930 lines
41 KiB
Go
930 lines
41 KiB
Go
package routes
|
||
|
||
import (
|
||
"bssapp-backend/models"
|
||
"bssapp-backend/queries"
|
||
"bssapp-backend/utils"
|
||
"context"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"fmt"
|
||
"math"
|
||
"net/http"
|
||
"sort"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/xuri/excelize/v2"
|
||
)
|
||
|
||
type productPerformanceExcelExportRequest struct {
|
||
Filters map[string][]string `json:"filters"`
|
||
SortBy string `json:"sort_by"`
|
||
Descending *bool `json:"descending"`
|
||
}
|
||
|
||
type productPerformanceExcelColumn struct {
|
||
Header string
|
||
Kind string
|
||
Value func(models.ProductPerformanceRow, models.ProductPerformanceGeneralRow, productPerformanceExcelStats) any
|
||
}
|
||
|
||
type productPerformanceExcelStats struct {
|
||
AvgSalesUSD90 float64
|
||
AvgSalesUSD180 float64
|
||
AvgSalesUSD365 float64
|
||
AvgSalesUSDTotal float64
|
||
}
|
||
|
||
func ExportProductPerformanceExcelHandler(pg *sql.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 180*time.Second)
|
||
defer cancel()
|
||
|
||
var req productPerformanceExcelExportRequest
|
||
if r.Method == http.MethodPost {
|
||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
http.Error(w, "gecersiz excel istegi: "+err.Error(), http.StatusBadRequest)
|
||
return
|
||
}
|
||
}
|
||
|
||
rows, _, err := queries.ListProductPerformance(ctx, pg, queries.ProductPerformanceFilters{
|
||
Limit: 50000,
|
||
Page: 1,
|
||
SortBy: "product_code",
|
||
Descending: false,
|
||
})
|
||
if err != nil {
|
||
http.Error(w, "urun performans excel listesi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
generalRows, err := queries.ListProductPerformanceGeneral(ctx, pg, 50000)
|
||
if err != nil {
|
||
http.Error(w, "urun performans genel excel listesi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
generalByKey := productPerformanceExcelGeneralMap(generalRows)
|
||
|
||
rows = filterProductPerformanceExcelRows(rows, generalByKey, req.Filters)
|
||
sortProductPerformanceExcelRows(rows, generalByKey, req.SortBy, req.Descending == nil || *req.Descending)
|
||
stats := productPerformanceExcelStatsFor(rows, generalByKey)
|
||
|
||
file, err := buildProductPerformanceExcelFile(rows, generalByKey, stats)
|
||
if err != nil {
|
||
http.Error(w, "urun performans excel dosyasi hazirlanamadi: "+err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer func() { _ = file.Close() }()
|
||
|
||
buf, err := file.WriteToBuffer()
|
||
if err != nil {
|
||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
filename := fmt.Sprintf("product_performance_color_yaka_%s.xlsx", time.Now().Format("20060102_150405"))
|
||
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||
w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")
|
||
w.Header().Set("Content-Length", fmt.Sprint(len(buf.Bytes())))
|
||
w.WriteHeader(http.StatusOK)
|
||
_, _ = w.Write(buf.Bytes())
|
||
}
|
||
}
|
||
|
||
func buildProductPerformanceExcelFile(rows []models.ProductPerformanceRow, generalByKey map[string]models.ProductPerformanceGeneralRow, stats productPerformanceExcelStats) (*excelize.File, error) {
|
||
f := excelize.NewFile()
|
||
sheet := "RenkYaka"
|
||
f.SetSheetName("Sheet1", sheet)
|
||
|
||
columns := productPerformanceExcelColumns()
|
||
headerStyle, _ := f.NewStyle(&excelize.Style{
|
||
Font: &excelize.Font{Bold: true, Color: "FFFFFF"},
|
||
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"1F4E78"}},
|
||
Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
|
||
})
|
||
textStyle, _ := f.NewStyle(&excelize.Style{NumFmt: 49})
|
||
intStyle, _ := f.NewStyle(&excelize.Style{NumFmt: 3})
|
||
decimalStyle, _ := f.NewStyle(&excelize.Style{NumFmt: 4})
|
||
percentStyle, _ := f.NewStyle(&excelize.Style{NumFmt: 10})
|
||
|
||
for i, col := range columns {
|
||
cell, _ := excelize.CoordinatesToCellName(i+1, 1)
|
||
_ = f.SetCellStr(sheet, cell, col.Header)
|
||
}
|
||
if len(columns) > 0 {
|
||
lastHeader, _ := excelize.CoordinatesToCellName(len(columns), 1)
|
||
_ = f.SetCellStyle(sheet, "A1", lastHeader, headerStyle)
|
||
}
|
||
|
||
for rowIndex, row := range rows {
|
||
excelRow := rowIndex + 2
|
||
general := generalByKey[productPerformanceExcelVariantKey(row.ProductCode, row.ColorCode, row.YakaKodu)]
|
||
for colIndex, col := range columns {
|
||
cell, _ := excelize.CoordinatesToCellName(colIndex+1, excelRow)
|
||
value := col.Value(row, general, stats)
|
||
switch col.Kind {
|
||
case "text":
|
||
_ = f.SetCellStr(sheet, cell, strings.TrimSpace(fmt.Sprint(value)))
|
||
default:
|
||
_ = f.SetCellValue(sheet, cell, productPerformanceExcelFloat(value))
|
||
}
|
||
}
|
||
}
|
||
|
||
lastRow := len(rows) + 1
|
||
if len(columns) > 0 {
|
||
filterLastRow := lastRow
|
||
if filterLastRow < 2 {
|
||
filterLastRow = 2
|
||
}
|
||
lastFilterCell, _ := excelize.CoordinatesToCellName(len(columns), filterLastRow)
|
||
_ = f.AutoFilter(sheet, "A1:"+lastFilterCell, []excelize.AutoFilterOptions{})
|
||
}
|
||
for i, col := range columns {
|
||
colName, _ := excelize.ColumnNumberToName(i + 1)
|
||
width := productPerformanceExcelColumnWidth(col.Header, col.Kind)
|
||
_ = f.SetColWidth(sheet, colName, colName, width)
|
||
if lastRow >= 2 {
|
||
style := decimalStyle
|
||
switch col.Kind {
|
||
case "text":
|
||
style = textStyle
|
||
case "int":
|
||
style = intStyle
|
||
case "percent":
|
||
style = percentStyle
|
||
}
|
||
_ = f.SetCellStyle(sheet, fmt.Sprintf("%s2", colName), fmt.Sprintf("%s%d", colName, lastRow), style)
|
||
}
|
||
}
|
||
_ = f.SetPanes(sheet, &excelize.Panes{
|
||
Freeze: true,
|
||
Split: false,
|
||
XSplit: 0,
|
||
YSplit: 1,
|
||
TopLeftCell: "A2",
|
||
ActivePane: "bottomLeft",
|
||
})
|
||
return f, nil
|
||
}
|
||
|
||
func productPerformanceExcelColumns() []productPerformanceExcelColumn {
|
||
return []productPerformanceExcelColumn{
|
||
{Header: "KPI Tarihi", Kind: "text", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.KpiDate
|
||
}},
|
||
{Header: "Ürün İlk Grubu", Kind: "text", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelCleanOptional(r.UrunIlkGrubu)
|
||
}},
|
||
{Header: "Askılı/Yan", Kind: "text", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelCleanOptional(r.AskiliYan)
|
||
}},
|
||
{Header: "Kategori", Kind: "text", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.Kategori
|
||
}},
|
||
{Header: "Ürün Ana Grubu", Kind: "text", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.UrunAnaGrubu
|
||
}},
|
||
{Header: "Ürün Alt Grubu", Kind: "text", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.UrunAltGrubu
|
||
}},
|
||
{Header: "Ürün", Kind: "text", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.ProductCode
|
||
}},
|
||
{Header: "Açıklama", Kind: "text", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.ItemDescription
|
||
}},
|
||
{Header: "Renk", Kind: "text", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.ColorCode
|
||
}},
|
||
{Header: "Renk Açıklama", Kind: "text", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.ColorDescription
|
||
}},
|
||
{Header: "Yaka", Kind: "text", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.YakaKodu
|
||
}},
|
||
{Header: "Renk/Yaka", Kind: "text", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelColorYaka(r)
|
||
}},
|
||
{Header: "Toplam Stok", Kind: "int", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.StockQty
|
||
}},
|
||
|
||
{Header: "90G Toplam Adet", Kind: "int", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.SalesQty90
|
||
}},
|
||
{Header: "90G Toplam Ciro USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.SalesUSD90
|
||
}},
|
||
{Header: "90G Ağ. Ort. Fiyat USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelAvgPrice(r.SalesUSD90, r.SalesQty90)
|
||
}},
|
||
{Header: "90G Ağ. Ort. Stok Gün", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.StockDays90
|
||
}},
|
||
{Header: "90G Stok Devir", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelFirstNonZero(r.StockTurnover90, productPerformanceExcelStockTurnover(r.SalesQty90, r.StockQty))
|
||
}},
|
||
{Header: "90G Ort. Taban Maliyet USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.BasePriceUSD
|
||
}},
|
||
{Header: "90G Ort. Çıplak Maliyet USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.CostPriceUSD
|
||
}},
|
||
{Header: "90G Ağ. Ort. Birim Taban K/Z USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelUnitProfit(r.SalesUSD90, r.SalesQty90, r.BasePriceUSD)
|
||
}},
|
||
{Header: "90G Ağ. Ort. Birim Çıplak K/Z USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelUnitProfit(r.SalesUSD90, r.SalesQty90, r.CostPriceUSD)
|
||
}},
|
||
{Header: "90G Taban Toplam K/Z USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelGrossProfit(r.SalesUSD90, r.SalesQty90, r.BasePriceUSD)
|
||
}},
|
||
{Header: "90G Çıplak Toplam K/Z USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelGrossProfit(r.SalesUSD90, r.SalesQty90, r.CostPriceUSD)
|
||
}},
|
||
{Header: "90G Taban Marj", Kind: "percent", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelMargin(r.SalesUSD90, r.SalesQty90, r.BasePriceUSD)
|
||
}},
|
||
{Header: "90G Çıplak Marj", Kind: "percent", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelMargin(r.SalesUSD90, r.SalesQty90, r.CostPriceUSD)
|
||
}},
|
||
{Header: "90G Tekil Piyasa", Kind: "int", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.MarketCount90
|
||
}},
|
||
{Header: "90G Tekil Müşteri", Kind: "int", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.CustomerCount90
|
||
}},
|
||
{Header: "90G Endeks", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, stats productPerformanceExcelStats) any {
|
||
return productPerformanceExcelFirstNonZero(r.SalesIndex90, productPerformanceExcelRelativeIndex(r.SalesUSD90, stats.AvgSalesUSD90))
|
||
}},
|
||
{Header: "90G Ürün Skor", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, stats productPerformanceExcelStats) any {
|
||
if strings.TrimSpace(r.PerformanceBucket) != "" {
|
||
return r.PerformanceScore
|
||
}
|
||
return productPerformanceExcelProductScore("90d", r.SalesUSD90, productPerformanceExcelRelativeIndex(r.SalesUSD90, stats.AvgSalesUSD90), productPerformanceExcelMargin(r.SalesUSD90, r.SalesQty90, r.CostPriceUSD), productPerformanceExcelStockTurnover(r.SalesQty90, r.StockQty), float64(r.MarketCount90), float64(r.CustomerCount90))
|
||
}},
|
||
|
||
{Header: "180G Toplam Adet", Kind: "int", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.SalesQty180
|
||
}},
|
||
{Header: "180G Toplam Ciro USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.SalesUSD180
|
||
}},
|
||
{Header: "180G Ağ. Ort. Fiyat USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelAvgPrice(r.SalesUSD180, r.SalesQty180)
|
||
}},
|
||
{Header: "180G Ağ. Ort. Stok Gün", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.StockDays180
|
||
}},
|
||
{Header: "180G Stok Devir", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelFirstNonZero(r.StockTurnover180, productPerformanceExcelStockTurnover(r.SalesQty180, r.StockQty))
|
||
}},
|
||
{Header: "180G Ort. Taban Maliyet USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.BasePriceUSD
|
||
}},
|
||
{Header: "180G Ort. Çıplak Maliyet USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.CostPriceUSD
|
||
}},
|
||
{Header: "180G Taban Toplam K/Z USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelGrossProfit(r.SalesUSD180, r.SalesQty180, r.BasePriceUSD)
|
||
}},
|
||
{Header: "180G Çıplak Toplam K/Z USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelGrossProfit(r.SalesUSD180, r.SalesQty180, r.CostPriceUSD)
|
||
}},
|
||
{Header: "180G Taban Marj", Kind: "percent", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelMargin(r.SalesUSD180, r.SalesQty180, r.BasePriceUSD)
|
||
}},
|
||
{Header: "180G Çıplak Marj", Kind: "percent", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelMargin(r.SalesUSD180, r.SalesQty180, r.CostPriceUSD)
|
||
}},
|
||
{Header: "180G Ürün Skor", Kind: "number", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, stats productPerformanceExcelStats) any {
|
||
return productPerformanceExcelProductScore("180d", r.SalesUSD180, productPerformanceExcelRelativeIndex(r.SalesUSD180, stats.AvgSalesUSD180), productPerformanceExcelMargin(r.SalesUSD180, r.SalesQty180, r.CostPriceUSD), productPerformanceExcelStockTurnover(r.SalesQty180, r.StockQty), productPerformanceExcelFirstNonZero(float64(g.MarketCountTotal), float64(r.MarketCount90)), productPerformanceExcelFirstNonZero(float64(g.CustomerCountTotal), float64(r.CustomerCount90)))
|
||
}},
|
||
|
||
{Header: "360G Toplam Adet", Kind: "int", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.SalesQty365
|
||
}},
|
||
{Header: "360G Toplam Ciro USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.SalesUSD365
|
||
}},
|
||
{Header: "360G Ağ. Ort. Fiyat USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelAvgPrice(r.SalesUSD365, r.SalesQty365)
|
||
}},
|
||
{Header: "360G Ağ. Ort. Stok Gün", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.StockDays365
|
||
}},
|
||
{Header: "360G Stok Devir", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelFirstNonZero(r.StockTurnover365, productPerformanceExcelStockTurnover(r.SalesQty365, r.StockQty))
|
||
}},
|
||
{Header: "360G Ort. Taban Maliyet USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.BasePriceUSD
|
||
}},
|
||
{Header: "360G Ort. Çıplak Maliyet USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.CostPriceUSD
|
||
}},
|
||
{Header: "360G Taban Toplam K/Z USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelGrossProfit(r.SalesUSD365, r.SalesQty365, r.BasePriceUSD)
|
||
}},
|
||
{Header: "360G Çıplak Toplam K/Z USD", Kind: "number", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelGrossProfit(r.SalesUSD365, r.SalesQty365, r.CostPriceUSD)
|
||
}},
|
||
{Header: "360G Taban Marj", Kind: "percent", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelMargin(r.SalesUSD365, r.SalesQty365, r.BasePriceUSD)
|
||
}},
|
||
{Header: "360G Çıplak Marj", Kind: "percent", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelMargin(r.SalesUSD365, r.SalesQty365, r.CostPriceUSD)
|
||
}},
|
||
{Header: "360G Ürün Skor", Kind: "number", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, stats productPerformanceExcelStats) any {
|
||
return productPerformanceExcelProductScore("365d", r.SalesUSD365, productPerformanceExcelRelativeIndex(r.SalesUSD365, stats.AvgSalesUSD365), productPerformanceExcelMargin(r.SalesUSD365, r.SalesQty365, r.CostPriceUSD), productPerformanceExcelStockTurnover(r.SalesQty365, r.StockQty), productPerformanceExcelFirstNonZero(float64(g.MarketCountTotal), float64(r.MarketCount90)), productPerformanceExcelFirstNonZero(float64(g.CustomerCountTotal), float64(r.CustomerCount90)))
|
||
}},
|
||
|
||
{Header: "Genel Toplam Adet", Kind: "int", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelTotalSalesQty(r, g)
|
||
}},
|
||
{Header: "Genel Toplam Ciro USD", Kind: "number", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelTotalSalesUSD(r, g)
|
||
}},
|
||
{Header: "Genel Ağ. Ort. Fiyat USD", Kind: "number", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelFirstNonZero(g.AvgPriceUSDTotal, productPerformanceExcelAvgPrice(productPerformanceExcelTotalSalesUSD(r, g), productPerformanceExcelTotalSalesQty(r, g)))
|
||
}},
|
||
{Header: "Genel Ağ. Ort. Stok Gün", Kind: "number", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelFirstNonZero(g.StockDaysTotal, r.StockDaysTotal)
|
||
}},
|
||
{Header: "Genel Stok Devir", Kind: "number", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelFirstNonZero(r.StockTurnoverTotal, productPerformanceExcelStockTurnover(productPerformanceExcelTotalSalesQty(r, g), r.StockQty))
|
||
}},
|
||
{Header: "Genel Ort. Taban Maliyet USD", Kind: "number", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelFirstNonZero(g.BasePriceUSD, r.BasePriceUSD)
|
||
}},
|
||
{Header: "Genel Ort. Çıplak Maliyet USD", Kind: "number", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelFirstNonZero(g.CostPriceUSD, r.CostPriceUSD)
|
||
}},
|
||
{Header: "Genel Taban Toplam K/Z USD", Kind: "number", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelGrossProfit(productPerformanceExcelTotalSalesUSD(r, g), productPerformanceExcelTotalSalesQty(r, g), productPerformanceExcelFirstNonZero(g.BasePriceUSD, r.BasePriceUSD))
|
||
}},
|
||
{Header: "Genel Çıplak Toplam K/Z USD", Kind: "number", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelGrossProfit(productPerformanceExcelTotalSalesUSD(r, g), productPerformanceExcelTotalSalesQty(r, g), productPerformanceExcelFirstNonZero(g.CostPriceUSD, r.CostPriceUSD))
|
||
}},
|
||
{Header: "Genel Taban Marj", Kind: "percent", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelMargin(productPerformanceExcelTotalSalesUSD(r, g), productPerformanceExcelTotalSalesQty(r, g), productPerformanceExcelFirstNonZero(g.BasePriceUSD, r.BasePriceUSD))
|
||
}},
|
||
{Header: "Genel Çıplak Marj", Kind: "percent", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelMargin(productPerformanceExcelTotalSalesUSD(r, g), productPerformanceExcelTotalSalesQty(r, g), productPerformanceExcelFirstNonZero(g.CostPriceUSD, r.CostPriceUSD))
|
||
}},
|
||
{Header: "Genel Tekil Piyasa", Kind: "int", Value: func(_ models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return g.MarketCountTotal
|
||
}},
|
||
{Header: "Genel Tekil Müşteri", Kind: "int", Value: func(_ models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return g.CustomerCountTotal
|
||
}},
|
||
{Header: "Genel Fatura", Kind: "int", Value: func(_ models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return g.InvoiceCountTotal
|
||
}},
|
||
{Header: "Genel Endeks", Kind: "number", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, stats productPerformanceExcelStats) any {
|
||
return productPerformanceExcelFirstNonZero(g.SalesIndexTotal, productPerformanceExcelRelativeIndex(productPerformanceExcelTotalSalesUSD(r, g), stats.AvgSalesUSDTotal))
|
||
}},
|
||
{Header: "Genel Ürün Skor", Kind: "number", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, stats productPerformanceExcelStats) any {
|
||
if strings.TrimSpace(g.PerformanceBucket) != "" {
|
||
return g.PerformanceScore
|
||
}
|
||
return productPerformanceExcelProductScore("total", productPerformanceExcelTotalSalesUSD(r, g), productPerformanceExcelRelativeIndex(productPerformanceExcelTotalSalesUSD(r, g), stats.AvgSalesUSDTotal), productPerformanceExcelMargin(productPerformanceExcelTotalSalesUSD(r, g), productPerformanceExcelTotalSalesQty(r, g), productPerformanceExcelFirstNonZero(g.CostPriceUSD, r.CostPriceUSD)), productPerformanceExcelStockTurnover(productPerformanceExcelTotalSalesQty(r, g), r.StockQty), float64(g.MarketCountTotal), float64(g.CustomerCountTotal))
|
||
}},
|
||
{Header: "Durum", Kind: "text", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelBucketLabel(productPerformanceExcelFirstNonEmpty(g.PerformanceBucket, r.PerformanceBucket))
|
||
}},
|
||
{Header: "Öneri", Kind: "text", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelFirstNonEmpty(g.Recommendation, r.Recommendation)
|
||
}},
|
||
{Header: "Son Satış", Kind: "text", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelFirstNonEmpty(g.LastSaleDate, r.LastSaleDate)
|
||
}},
|
||
{Header: "Son Ref", Kind: "text", Value: func(r models.ProductPerformanceRow, g models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return productPerformanceExcelFirstNonEmpty(g.LastRefNumber, r.LastRefNumber)
|
||
}},
|
||
{Header: "Güncelleme", Kind: "text", Value: func(r models.ProductPerformanceRow, _ models.ProductPerformanceGeneralRow, _ productPerformanceExcelStats) any {
|
||
return r.UpdatedAt
|
||
}},
|
||
}
|
||
}
|
||
|
||
func productPerformanceExcelGeneralMap(rows []models.ProductPerformanceGeneralRow) map[string]models.ProductPerformanceGeneralRow {
|
||
out := make(map[string]models.ProductPerformanceGeneralRow, len(rows))
|
||
for _, row := range rows {
|
||
out[productPerformanceExcelVariantKey(row.ProductCode, row.ColorCode, row.YakaKodu)] = row
|
||
}
|
||
return out
|
||
}
|
||
|
||
func filterProductPerformanceExcelRows(rows []models.ProductPerformanceRow, generalByKey map[string]models.ProductPerformanceGeneralRow, filters map[string][]string) []models.ProductPerformanceRow {
|
||
if len(filters) == 0 {
|
||
return rows
|
||
}
|
||
out := make([]models.ProductPerformanceRow, 0, len(rows))
|
||
for _, row := range rows {
|
||
general := generalByKey[productPerformanceExcelVariantKey(row.ProductCode, row.ColorCode, row.YakaKodu)]
|
||
if productPerformanceExcelRowMatchesFilters(row, general, filters) {
|
||
out = append(out, row)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
func productPerformanceExcelRowMatchesFilters(row models.ProductPerformanceRow, general models.ProductPerformanceGeneralRow, filters map[string][]string) bool {
|
||
for field, values := range filters {
|
||
selected := productPerformanceExcelSelectedSet(values)
|
||
if len(selected) == 0 {
|
||
continue
|
||
}
|
||
matched := false
|
||
for _, candidate := range productPerformanceExcelFilterCandidates(row, general, field) {
|
||
if selected[productPerformanceExcelNormalize(candidate)] {
|
||
matched = true
|
||
break
|
||
}
|
||
}
|
||
if !matched {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
func productPerformanceExcelSelectedSet(values []string) map[string]bool {
|
||
out := make(map[string]bool, len(values))
|
||
for _, value := range values {
|
||
value = productPerformanceExcelNormalize(value)
|
||
if value != "" {
|
||
out[value] = true
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
func productPerformanceExcelFilterCandidates(row models.ProductPerformanceRow, general models.ProductPerformanceGeneralRow, field string) []string {
|
||
switch strings.TrimSpace(field) {
|
||
case "product_code":
|
||
return []string{row.ProductCode}
|
||
case "item_description":
|
||
return []string{row.ItemDescription}
|
||
case "color_code":
|
||
return []string{row.ColorCode}
|
||
case "yaka_kodu":
|
||
return []string{row.YakaKodu}
|
||
case "color_yaka":
|
||
return []string{productPerformanceExcelColorYaka(row), row.ColorCode + "/" + row.YakaKodu}
|
||
case "kategori":
|
||
return []string{row.Kategori}
|
||
case "askili_yan":
|
||
return []string{productPerformanceExcelCleanOptional(row.AskiliYan)}
|
||
case "urun_ilk_grubu":
|
||
return []string{productPerformanceExcelCleanOptional(row.UrunIlkGrubu)}
|
||
case "urun_ana_grubu":
|
||
return []string{productPerformanceExcelFirstNonEmpty(general.UrunAnaGrubu, row.UrunAnaGrubu)}
|
||
case "urun_alt_grubu":
|
||
return []string{row.UrunAltGrubu}
|
||
case "market_key":
|
||
return []string{row.MarketKey, productPerformanceExcelMarketName(row.MarketKey)}
|
||
case "performance_bucket":
|
||
bucket := productPerformanceExcelFirstNonEmpty(general.PerformanceBucket, row.PerformanceBucket)
|
||
return []string{bucket, productPerformanceExcelBucketLabel(bucket)}
|
||
default:
|
||
return nil
|
||
}
|
||
}
|
||
|
||
func sortProductPerformanceExcelRows(rows []models.ProductPerformanceRow, generalByKey map[string]models.ProductPerformanceGeneralRow, sortBy string, desc bool) {
|
||
sortBy = strings.TrimSpace(sortBy)
|
||
if sortBy == "" || sortBy == "image" {
|
||
sortBy = "product_code"
|
||
desc = false
|
||
}
|
||
sort.SliceStable(rows, func(i, j int) bool {
|
||
left := rows[i]
|
||
right := rows[j]
|
||
leftGen := generalByKey[productPerformanceExcelVariantKey(left.ProductCode, left.ColorCode, left.YakaKodu)]
|
||
rightGen := generalByKey[productPerformanceExcelVariantKey(right.ProductCode, right.ColorCode, right.YakaKodu)]
|
||
cmp := productPerformanceExcelCompareSort(left, leftGen, right, rightGen, sortBy)
|
||
if cmp == 0 {
|
||
cmp = strings.Compare(productPerformanceExcelHierarchySortKey(left), productPerformanceExcelHierarchySortKey(right))
|
||
}
|
||
if desc {
|
||
return cmp > 0
|
||
}
|
||
return cmp < 0
|
||
})
|
||
}
|
||
|
||
func productPerformanceExcelCompareSort(left models.ProductPerformanceRow, leftGen models.ProductPerformanceGeneralRow, right models.ProductPerformanceRow, rightGen models.ProductPerformanceGeneralRow, sortBy string) int {
|
||
leftNum, rightNum, numeric := productPerformanceExcelSortNumeric(left, leftGen, sortBy), productPerformanceExcelSortNumeric(right, rightGen, sortBy), productPerformanceExcelIsNumericSort(sortBy)
|
||
if numeric {
|
||
if leftNum < rightNum {
|
||
return -1
|
||
}
|
||
if leftNum > rightNum {
|
||
return 1
|
||
}
|
||
return 0
|
||
}
|
||
leftText := productPerformanceExcelSortText(left, leftGen, sortBy)
|
||
rightText := productPerformanceExcelSortText(right, rightGen, sortBy)
|
||
return strings.Compare(productPerformanceExcelNormalize(leftText), productPerformanceExcelNormalize(rightText))
|
||
}
|
||
|
||
func productPerformanceExcelIsNumericSort(sortBy string) bool {
|
||
switch sortBy {
|
||
case "stock_qty", "sales_qty_90d", "sales_qty_180d", "sales_qty_365d", "sales_qty_total",
|
||
"sales_usd_90d", "sales_usd_180d", "sales_usd_365d", "sales_usd_total",
|
||
"avg_price_usd_90d", "avg_price_usd_180d", "avg_price_usd_365d", "avg_price_usd_total",
|
||
"stock_days_90d", "stock_days_180d", "stock_days_365d", "stock_days_total",
|
||
"stock_turnover_90d", "stock_turnover_180d", "stock_turnover_365d", "stock_turnover_total",
|
||
"base_price_usd", "cost_price_usd", "market_count_90d", "customer_count_90d", "market_count_total", "customer_count_total",
|
||
"sales_index_90d", "sales_index_total", "performance_score", "performance_score_90d", "performance_score_total":
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
func productPerformanceExcelSortNumeric(row models.ProductPerformanceRow, general models.ProductPerformanceGeneralRow, sortBy string) float64 {
|
||
switch sortBy {
|
||
case "stock_qty":
|
||
return row.StockQty
|
||
case "sales_qty_90d":
|
||
return row.SalesQty90
|
||
case "sales_qty_180d":
|
||
return row.SalesQty180
|
||
case "sales_qty_365d":
|
||
return row.SalesQty365
|
||
case "sales_qty_total":
|
||
return productPerformanceExcelTotalSalesQty(row, general)
|
||
case "sales_usd_90d":
|
||
return row.SalesUSD90
|
||
case "sales_usd_180d":
|
||
return row.SalesUSD180
|
||
case "sales_usd_365d":
|
||
return row.SalesUSD365
|
||
case "sales_usd_total":
|
||
return productPerformanceExcelTotalSalesUSD(row, general)
|
||
case "avg_price_usd_90d":
|
||
return productPerformanceExcelAvgPrice(row.SalesUSD90, row.SalesQty90)
|
||
case "avg_price_usd_180d":
|
||
return productPerformanceExcelAvgPrice(row.SalesUSD180, row.SalesQty180)
|
||
case "avg_price_usd_365d":
|
||
return productPerformanceExcelAvgPrice(row.SalesUSD365, row.SalesQty365)
|
||
case "avg_price_usd_total":
|
||
return productPerformanceExcelFirstNonZero(general.AvgPriceUSDTotal, productPerformanceExcelAvgPrice(productPerformanceExcelTotalSalesUSD(row, general), productPerformanceExcelTotalSalesQty(row, general)))
|
||
case "stock_days_90d":
|
||
return row.StockDays90
|
||
case "stock_days_180d":
|
||
return row.StockDays180
|
||
case "stock_days_365d":
|
||
return row.StockDays365
|
||
case "stock_days_total":
|
||
return productPerformanceExcelFirstNonZero(general.StockDaysTotal, row.StockDaysTotal)
|
||
case "stock_turnover_90d":
|
||
return row.StockTurnover90
|
||
case "stock_turnover_180d":
|
||
return row.StockTurnover180
|
||
case "stock_turnover_365d":
|
||
return row.StockTurnover365
|
||
case "stock_turnover_total":
|
||
return row.StockTurnoverTotal
|
||
case "base_price_usd":
|
||
return productPerformanceExcelFirstNonZero(general.BasePriceUSD, row.BasePriceUSD)
|
||
case "cost_price_usd":
|
||
return productPerformanceExcelFirstNonZero(general.CostPriceUSD, row.CostPriceUSD)
|
||
case "market_count_90d":
|
||
return float64(row.MarketCount90)
|
||
case "customer_count_90d":
|
||
return float64(row.CustomerCount90)
|
||
case "market_count_total":
|
||
return float64(general.MarketCountTotal)
|
||
case "customer_count_total":
|
||
return float64(general.CustomerCountTotal)
|
||
case "sales_index_90d":
|
||
return row.SalesIndex90
|
||
case "sales_index_total":
|
||
return general.SalesIndexTotal
|
||
case "performance_score", "performance_score_90d":
|
||
return row.PerformanceScore
|
||
case "performance_score_total":
|
||
return general.PerformanceScore
|
||
default:
|
||
return 0
|
||
}
|
||
}
|
||
|
||
func productPerformanceExcelSortText(row models.ProductPerformanceRow, general models.ProductPerformanceGeneralRow, sortBy string) string {
|
||
switch sortBy {
|
||
case "product_code":
|
||
return row.ProductCode
|
||
case "color_yaka":
|
||
return productPerformanceExcelColorYaka(row)
|
||
case "color_code":
|
||
return row.ColorCode
|
||
case "yaka_kodu":
|
||
return row.YakaKodu
|
||
case "item_description":
|
||
return row.ItemDescription
|
||
case "kategori":
|
||
return row.Kategori
|
||
case "askili_yan":
|
||
return productPerformanceExcelCleanOptional(row.AskiliYan)
|
||
case "urun_ilk_grubu":
|
||
return productPerformanceExcelCleanOptional(row.UrunIlkGrubu)
|
||
case "urun_ana_grubu":
|
||
return productPerformanceExcelFirstNonEmpty(general.UrunAnaGrubu, row.UrunAnaGrubu)
|
||
case "urun_alt_grubu":
|
||
return row.UrunAltGrubu
|
||
case "market_key":
|
||
return productPerformanceExcelMarketName(row.MarketKey)
|
||
case "performance_bucket":
|
||
return productPerformanceExcelBucketLabel(productPerformanceExcelFirstNonEmpty(general.PerformanceBucket, row.PerformanceBucket))
|
||
default:
|
||
return productPerformanceExcelHierarchySortKey(row)
|
||
}
|
||
}
|
||
|
||
func productPerformanceExcelHierarchySortKey(row models.ProductPerformanceRow) string {
|
||
return strings.Join([]string{
|
||
productPerformanceExcelCleanOptional(row.UrunIlkGrubu),
|
||
productPerformanceExcelCleanOptional(row.AskiliYan),
|
||
row.Kategori,
|
||
row.UrunAnaGrubu,
|
||
row.UrunAltGrubu,
|
||
row.ProductCode,
|
||
row.ColorCode,
|
||
row.YakaKodu,
|
||
}, "|")
|
||
}
|
||
|
||
func productPerformanceExcelStatsFor(rows []models.ProductPerformanceRow, generalByKey map[string]models.ProductPerformanceGeneralRow) productPerformanceExcelStats {
|
||
var stats productPerformanceExcelStats
|
||
var c90, c180, c365, cTotal float64
|
||
for _, row := range rows {
|
||
general := generalByKey[productPerformanceExcelVariantKey(row.ProductCode, row.ColorCode, row.YakaKodu)]
|
||
if row.SalesUSD90 > 0 {
|
||
stats.AvgSalesUSD90 += row.SalesUSD90
|
||
c90++
|
||
}
|
||
if row.SalesUSD180 > 0 {
|
||
stats.AvgSalesUSD180 += row.SalesUSD180
|
||
c180++
|
||
}
|
||
if row.SalesUSD365 > 0 {
|
||
stats.AvgSalesUSD365 += row.SalesUSD365
|
||
c365++
|
||
}
|
||
if total := productPerformanceExcelTotalSalesUSD(row, general); total > 0 {
|
||
stats.AvgSalesUSDTotal += total
|
||
cTotal++
|
||
}
|
||
}
|
||
if c90 > 0 {
|
||
stats.AvgSalesUSD90 /= c90
|
||
}
|
||
if c180 > 0 {
|
||
stats.AvgSalesUSD180 /= c180
|
||
}
|
||
if c365 > 0 {
|
||
stats.AvgSalesUSD365 /= c365
|
||
}
|
||
if cTotal > 0 {
|
||
stats.AvgSalesUSDTotal /= cTotal
|
||
}
|
||
return stats
|
||
}
|
||
|
||
func productPerformanceExcelVariantKey(productCode, colorCode, yakaKodu string) string {
|
||
return strings.ToUpper(strings.TrimSpace(productCode)) + "|" + strings.ToUpper(strings.TrimSpace(colorCode)) + "|" + strings.ToUpper(strings.TrimSpace(yakaKodu))
|
||
}
|
||
|
||
func productPerformanceExcelColorYaka(row models.ProductPerformanceRow) string {
|
||
color := strings.TrimSpace(row.ColorCode)
|
||
if desc := strings.TrimSpace(row.ColorDescription); color != "" && desc != "" {
|
||
color = color + "-" + strings.ToUpper(desc)
|
||
}
|
||
yaka := strings.TrimSpace(row.YakaKodu)
|
||
parts := make([]string, 0, 2)
|
||
if color != "" && color != "-" {
|
||
parts = append(parts, color)
|
||
}
|
||
if yaka != "" && yaka != "-" {
|
||
parts = append(parts, yaka)
|
||
}
|
||
return strings.Join(parts, "/")
|
||
}
|
||
|
||
func productPerformanceExcelMarketName(value string) string {
|
||
parts := strings.Split(value, "|")
|
||
for i := len(parts) - 1; i >= 0; i-- {
|
||
part := strings.TrimSpace(parts[i])
|
||
if part != "" {
|
||
return part
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func productPerformanceExcelBucketLabel(value string) string {
|
||
switch strings.TrimSpace(value) {
|
||
case "YILDIZ_URUN":
|
||
return "Yıldız Ürün"
|
||
case "STOKSUZ_TALEP":
|
||
return "Stoksuz Talep"
|
||
case "STOK_RISKI":
|
||
return "Stok Riski"
|
||
case "FIYAT_BASKISI":
|
||
return "Fiyat Baskısı"
|
||
case "FIYAT_FIRSATI":
|
||
return "Fiyat Fırsatı"
|
||
case "MALIYET_YOK":
|
||
return "Maliyet Yok"
|
||
case "TAKIP":
|
||
return "Takip"
|
||
default:
|
||
return strings.TrimSpace(value)
|
||
}
|
||
}
|
||
|
||
func productPerformanceExcelCleanOptional(value string) string {
|
||
value = strings.TrimSpace(value)
|
||
if value == "-" {
|
||
return ""
|
||
}
|
||
return value
|
||
}
|
||
|
||
func productPerformanceExcelNormalize(value string) string {
|
||
return strings.ToUpper(strings.TrimSpace(value))
|
||
}
|
||
|
||
func productPerformanceExcelFirstNonEmpty(values ...string) string {
|
||
for _, value := range values {
|
||
value = strings.TrimSpace(value)
|
||
if value != "" {
|
||
return value
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func productPerformanceExcelFirstNonZero(values ...float64) float64 {
|
||
for _, value := range values {
|
||
if value != 0 && !math.IsNaN(value) && !math.IsInf(value, 0) {
|
||
return value
|
||
}
|
||
}
|
||
return 0
|
||
}
|
||
|
||
func productPerformanceExcelTotalSalesQty(row models.ProductPerformanceRow, general models.ProductPerformanceGeneralRow) float64 {
|
||
return productPerformanceExcelFirstNonZero(general.SalesQtyTotal, row.SalesQtyTotal)
|
||
}
|
||
|
||
func productPerformanceExcelTotalSalesUSD(row models.ProductPerformanceRow, general models.ProductPerformanceGeneralRow) float64 {
|
||
return productPerformanceExcelFirstNonZero(general.SalesUSDTotal, row.SalesUSDTotal)
|
||
}
|
||
|
||
func productPerformanceExcelAvgPrice(salesUSD, qty float64) float64 {
|
||
if qty == 0 {
|
||
return 0
|
||
}
|
||
return salesUSD / qty
|
||
}
|
||
|
||
func productPerformanceExcelStockTurnover(salesQty, stockQty float64) float64 {
|
||
if stockQty <= 0 {
|
||
return 0
|
||
}
|
||
return salesQty / stockQty
|
||
}
|
||
|
||
func productPerformanceExcelUnitProfit(salesUSD, qty, unitCost float64) float64 {
|
||
if qty <= 0 {
|
||
return 0
|
||
}
|
||
return (salesUSD / qty) - unitCost
|
||
}
|
||
|
||
func productPerformanceExcelGrossProfit(salesUSD, qty, unitCost float64) float64 {
|
||
if qty <= 0 {
|
||
return 0
|
||
}
|
||
return salesUSD - qty*unitCost
|
||
}
|
||
|
||
func productPerformanceExcelMargin(salesUSD, qty, unitCost float64) float64 {
|
||
if salesUSD == 0 {
|
||
return 0
|
||
}
|
||
return productPerformanceExcelGrossProfit(salesUSD, qty, unitCost) / salesUSD
|
||
}
|
||
|
||
func productPerformanceExcelRelativeIndex(value, average float64) float64 {
|
||
if value <= 0 || average <= 0 {
|
||
return 0
|
||
}
|
||
return value / average
|
||
}
|
||
|
||
func productPerformanceExcelProductScore(suffix string, salesUSD, salesIndex, margin, stockTurnover, marketCount, customerCount float64) float64 {
|
||
revenue := productPerformanceExcelRatioScore(salesIndex, 2)
|
||
if revenue == 0 {
|
||
revenue = productPerformanceExcelRatioScore(salesUSD, productPerformanceExcelProductRevenueTarget(suffix))
|
||
}
|
||
score := 0.30*productPerformanceExcelMarginScore(margin) +
|
||
0.20*productPerformanceExcelRatioScore(stockTurnover, 1.50) +
|
||
0.20*revenue +
|
||
0.15*productPerformanceExcelRatioScore(marketCount, 8) +
|
||
0.15*productPerformanceExcelRatioScore(customerCount, 25)
|
||
return productPerformanceExcelRoundScore(score)
|
||
}
|
||
|
||
func productPerformanceExcelProductRevenueTarget(suffix string) float64 {
|
||
switch suffix {
|
||
case "180d":
|
||
return 20000
|
||
case "365d":
|
||
return 40000
|
||
case "total":
|
||
return 120000
|
||
default:
|
||
return 10000
|
||
}
|
||
}
|
||
|
||
func productPerformanceExcelMarginScore(margin float64) float64 {
|
||
if margin < 0 {
|
||
margin = 0
|
||
}
|
||
return productPerformanceExcelRatioScore(margin, 0.40)
|
||
}
|
||
|
||
func productPerformanceExcelRatioScore(value, target float64) float64 {
|
||
if target <= 0 || value <= 0 {
|
||
return 0
|
||
}
|
||
score := value * 100 / target
|
||
if score > 100 {
|
||
return 100
|
||
}
|
||
if score < 0 {
|
||
return 0
|
||
}
|
||
return score
|
||
}
|
||
|
||
func productPerformanceExcelRoundScore(score float64) float64 {
|
||
if score < 0 {
|
||
score = 0
|
||
}
|
||
if score > 100 {
|
||
score = 100
|
||
}
|
||
return math.Round(score*10000) / 10000
|
||
}
|
||
|
||
func productPerformanceExcelFloat(value any) float64 {
|
||
switch v := value.(type) {
|
||
case float64:
|
||
if math.IsNaN(v) || math.IsInf(v, 0) {
|
||
return 0
|
||
}
|
||
return v
|
||
case float32:
|
||
return productPerformanceExcelFloat(float64(v))
|
||
case int:
|
||
return float64(v)
|
||
case int64:
|
||
return float64(v)
|
||
case int32:
|
||
return float64(v)
|
||
case uint:
|
||
return float64(v)
|
||
case uint64:
|
||
return float64(v)
|
||
case uint32:
|
||
return float64(v)
|
||
default:
|
||
return 0
|
||
}
|
||
}
|
||
|
||
func productPerformanceExcelColumnWidth(header, kind string) float64 {
|
||
if kind == "text" {
|
||
if len([]rune(header)) > 14 {
|
||
return 24
|
||
}
|
||
return 16
|
||
}
|
||
if strings.Contains(header, "Marj") || strings.Contains(header, "Skor") || strings.Contains(header, "Endeks") {
|
||
return 14
|
||
}
|
||
return 16
|
||
}
|