Merge remote-tracking branch 'origin/master'
This commit is contained in:
603
svc/routes/product_series.go
Normal file
603
svc/routes/product_series.go
Normal file
@@ -0,0 +1,603 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"bssapp-backend/db"
|
||||
"bssapp-backend/queries"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type productSeriesDefinition struct {
|
||||
ID int64 `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Title string `json:"title"`
|
||||
IsActive bool `json:"is_active"`
|
||||
ParentFilter string `json:"parent_filter"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
type productSeriesStockRawRow struct {
|
||||
ProductCode string
|
||||
ProductDescription string
|
||||
ColorCode string
|
||||
ColorTitle string
|
||||
Dim3Code string
|
||||
SizeCode string
|
||||
Qty float64
|
||||
UrunAnaGrubu string
|
||||
UrunAltGrubu string
|
||||
Marka string
|
||||
DropVal string
|
||||
Fit string
|
||||
UrunIcerigi string
|
||||
Kategori string
|
||||
}
|
||||
|
||||
type productSeriesMappingRow struct {
|
||||
RowKey string `json:"row_key"`
|
||||
ProductCode string `json:"product_code"`
|
||||
ProductDescription string `json:"product_description"`
|
||||
ColorCode string `json:"color_code"`
|
||||
ColorTitle string `json:"color_title"`
|
||||
Dim3Code string `json:"dim3_code"`
|
||||
UrunAnaGrubu string `json:"urun_ana_grubu"`
|
||||
UrunAltGrubu string `json:"urun_alt_grubu"`
|
||||
Marka string `json:"marka"`
|
||||
DropVal string `json:"drop_val"`
|
||||
Fit string `json:"fit"`
|
||||
UrunIcerigi string `json:"urun_icerigi"`
|
||||
Kategori string `json:"kategori"`
|
||||
SizeQty map[string]float64 `json:"size_qty"`
|
||||
TotalQty float64 `json:"total_qty"`
|
||||
SeriesIDs []int64 `json:"series_ids"`
|
||||
Series []productSeriesDefinition `json:"series"`
|
||||
MmitemID int64 `json:"mmitem_id"`
|
||||
Dim1ID int64 `json:"dim1_id"`
|
||||
Dim3ID int64 `json:"dim3_id"`
|
||||
MappingReady bool `json:"mapping_ready"`
|
||||
MappingWarning string `json:"mapping_warning"`
|
||||
}
|
||||
|
||||
type productSeriesMappingsResponse struct {
|
||||
Rows []productSeriesMappingRow `json:"rows"`
|
||||
SizeColumns []string `json:"size_columns"`
|
||||
Definitions []productSeriesDefinition `json:"definitions"`
|
||||
}
|
||||
|
||||
func GetProductSeriesDefinitionsHandler(pg *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
defs, err := listProductSeriesDefinitions(ctx, pg, false)
|
||||
if err != nil {
|
||||
http.Error(w, "Seri tanimlari alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, defs)
|
||||
}
|
||||
}
|
||||
|
||||
func PostProductSeriesDefinitionHandler(pg *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req productSeriesDefinition
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Gecersiz istek", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
req.Code = strings.TrimSpace(req.Code)
|
||||
req.Title = strings.TrimSpace(req.Title)
|
||||
if req.Code == "" {
|
||||
http.Error(w, "Seri kodu zorunludur", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
err := pg.QueryRowContext(ctx, `
|
||||
INSERT INTO dfgrp (code, title, is_active, typ, master, parent_filter, sort_order, is_required, notes)
|
||||
VALUES ($1, $2, COALESCE($3, TRUE), 'opt', 'zbggseri', $4, $5, FALSE, $6)
|
||||
RETURNING id
|
||||
`, req.Code, req.Title, req.IsActive, req.ParentFilter, req.SortOrder, req.Notes).Scan(&req.ID)
|
||||
if err != nil {
|
||||
http.Error(w, "Seri tanimi eklenemedi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, req)
|
||||
}
|
||||
}
|
||||
|
||||
func PutProductSeriesDefinitionHandler(pg *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id, _ := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
|
||||
if id <= 0 {
|
||||
http.Error(w, "Gecersiz id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var req productSeriesDefinition
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Gecersiz istek", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
req.Code = strings.TrimSpace(req.Code)
|
||||
req.Title = strings.TrimSpace(req.Title)
|
||||
if req.Code == "" {
|
||||
http.Error(w, "Seri kodu zorunludur", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
res, err := pg.ExecContext(ctx, `
|
||||
UPDATE dfgrp
|
||||
SET code=$2,
|
||||
title=$3,
|
||||
is_active=$4,
|
||||
parent_filter=$5,
|
||||
sort_order=$6,
|
||||
notes=$7
|
||||
WHERE id=$1 AND master='zbggseri'
|
||||
`, id, req.Code, req.Title, req.IsActive, req.ParentFilter, req.SortOrder, req.Notes)
|
||||
if err != nil {
|
||||
http.Error(w, "Seri tanimi guncellenemedi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if n, _ := res.RowsAffected(); n == 0 {
|
||||
http.Error(w, "Seri tanimi bulunamadi", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
req.ID = id
|
||||
writeJSON(w, req)
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteProductSeriesDefinitionHandler(pg *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id, _ := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
|
||||
if id <= 0 {
|
||||
http.Error(w, "Gecersiz id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
if _, err := pg.ExecContext(ctx, `UPDATE dfgrp SET is_active=FALSE WHERE id=$1 AND master='zbggseri'`, id); err != nil {
|
||||
http.Error(w, "Seri tanimi silinemedi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]any{"ok": true})
|
||||
}
|
||||
}
|
||||
|
||||
func GetProductSeriesMappingsHandler(pg *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
f := readStockAttrFilters(r)
|
||||
limit := 0
|
||||
if raw := strings.TrimSpace(r.URL.Query().Get("limit")); raw != "" {
|
||||
if n, err := strconv.Atoi(raw); err == nil && n >= 0 {
|
||||
limit = n
|
||||
}
|
||||
}
|
||||
search := firstNonEmpty(r.URL.Query().Get("q"), r.URL.Query().Get("code"), r.URL.Query().Get("product_code"))
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 120*time.Second)
|
||||
defer cancel()
|
||||
|
||||
msRows, err := db.MssqlDB.QueryContext(ctx, queries.GetProductSeriesStockRowsQuery,
|
||||
f.kategori,
|
||||
f.urunAnaGrubu,
|
||||
joinFilterValues(f.urunAltGrubu),
|
||||
joinFilterValues(f.renk),
|
||||
joinFilterValues(f.renk2),
|
||||
joinFilterValues(f.urunIcerigi),
|
||||
joinFilterValues(f.fit),
|
||||
joinFilterValues(f.drop),
|
||||
joinFilterValues(f.beden),
|
||||
strconv.Itoa(limit),
|
||||
search,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, "Stok seri listesi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer msRows.Close()
|
||||
|
||||
grouped := map[string]*productSeriesMappingRow{}
|
||||
codeSet := map[string]struct{}{}
|
||||
colorSet := map[string]struct{}{}
|
||||
dim3Set := map[string]struct{}{}
|
||||
sizeSet := map[string]struct{}{}
|
||||
for msRows.Next() {
|
||||
var raw productSeriesStockRawRow
|
||||
if err := msRows.Scan(
|
||||
&raw.ProductCode,
|
||||
&raw.ProductDescription,
|
||||
&raw.ColorCode,
|
||||
&raw.ColorTitle,
|
||||
&raw.Dim3Code,
|
||||
&raw.SizeCode,
|
||||
&raw.Qty,
|
||||
&raw.UrunAnaGrubu,
|
||||
&raw.UrunAltGrubu,
|
||||
&raw.Marka,
|
||||
&raw.DropVal,
|
||||
&raw.Fit,
|
||||
&raw.UrunIcerigi,
|
||||
&raw.Kategori,
|
||||
); err != nil {
|
||||
http.Error(w, "Stok satiri okunamadi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
raw.ProductCode = strings.TrimSpace(raw.ProductCode)
|
||||
raw.ColorCode = strings.TrimSpace(raw.ColorCode)
|
||||
raw.Dim3Code = strings.TrimSpace(raw.Dim3Code)
|
||||
raw.SizeCode = strings.TrimSpace(raw.SizeCode)
|
||||
key := productSeriesKey(raw.ProductCode, raw.ColorCode, raw.Dim3Code)
|
||||
row := grouped[key]
|
||||
if row == nil {
|
||||
row = &productSeriesMappingRow{
|
||||
RowKey: key,
|
||||
ProductCode: raw.ProductCode,
|
||||
ProductDescription: strings.TrimSpace(raw.ProductDescription),
|
||||
ColorCode: raw.ColorCode,
|
||||
ColorTitle: strings.TrimSpace(raw.ColorTitle),
|
||||
Dim3Code: raw.Dim3Code,
|
||||
UrunAnaGrubu: strings.TrimSpace(raw.UrunAnaGrubu),
|
||||
UrunAltGrubu: strings.TrimSpace(raw.UrunAltGrubu),
|
||||
Marka: strings.TrimSpace(raw.Marka),
|
||||
DropVal: strings.TrimSpace(raw.DropVal),
|
||||
Fit: strings.TrimSpace(raw.Fit),
|
||||
UrunIcerigi: strings.TrimSpace(raw.UrunIcerigi),
|
||||
Kategori: strings.TrimSpace(raw.Kategori),
|
||||
SizeQty: map[string]float64{},
|
||||
SeriesIDs: []int64{},
|
||||
Series: []productSeriesDefinition{},
|
||||
}
|
||||
grouped[key] = row
|
||||
codeSet[raw.ProductCode] = struct{}{}
|
||||
colorSet[raw.ColorCode] = struct{}{}
|
||||
if raw.Dim3Code != "" {
|
||||
dim3Set[raw.Dim3Code] = struct{}{}
|
||||
}
|
||||
}
|
||||
if raw.SizeCode != "" {
|
||||
row.SizeQty[raw.SizeCode] = raw.Qty
|
||||
sizeSet[raw.SizeCode] = struct{}{}
|
||||
}
|
||||
row.TotalQty += raw.Qty
|
||||
}
|
||||
if err := msRows.Err(); err != nil {
|
||||
http.Error(w, "Stok satirlari okunamadi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
defs, err := listProductSeriesDefinitions(ctx, pg, true)
|
||||
if err != nil {
|
||||
http.Error(w, "Seri tanimlari alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defByID := make(map[int64]productSeriesDefinition, len(defs))
|
||||
for _, d := range defs {
|
||||
defByID[d.ID] = d
|
||||
}
|
||||
|
||||
codes := setToSortedSlice(codeSet)
|
||||
mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes)
|
||||
dim1ByToken, _ := loadDimTokenIDs(ctx, pg, "dimval1", setToSortedSlice(colorSet))
|
||||
dim3ByToken, _ := loadDimTokenIDs(ctx, pg, "dimval3", setToSortedSlice(dim3Set))
|
||||
existing, _ := loadProductSeriesAssignments(ctx, pg, codes)
|
||||
|
||||
out := make([]productSeriesMappingRow, 0, len(grouped))
|
||||
for _, row := range grouped {
|
||||
row.MmitemID = mmitemByCode[row.ProductCode]
|
||||
row.Dim1ID = dim1ByToken[row.ColorCode]
|
||||
if row.Dim3Code != "" {
|
||||
row.Dim3ID = dim3ByToken[row.Dim3Code]
|
||||
}
|
||||
row.MappingReady = row.MmitemID > 0 && row.Dim1ID > 0 && (row.Dim3Code == "" || row.Dim3ID > 0)
|
||||
if !row.MappingReady {
|
||||
row.MappingWarning = "PG urun veya varyant token eslesmesi bulunamadi"
|
||||
}
|
||||
assignKey := assignmentKey(row.ProductCode, row.Dim1ID, row.Dim3ID)
|
||||
for _, id := range existing[assignKey] {
|
||||
if d, ok := defByID[id]; ok {
|
||||
row.SeriesIDs = append(row.SeriesIDs, id)
|
||||
row.Series = append(row.Series, d)
|
||||
}
|
||||
}
|
||||
sort.Slice(row.Series, func(i, j int) bool { return row.Series[i].Code < row.Series[j].Code })
|
||||
sort.Slice(row.SeriesIDs, func(i, j int) bool { return row.SeriesIDs[i] < row.SeriesIDs[j] })
|
||||
out = append(out, *row)
|
||||
}
|
||||
productTotals := productSeriesTotalQtyByCode(out)
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
totalI := productTotals[out[i].ProductCode]
|
||||
totalJ := productTotals[out[j].ProductCode]
|
||||
if totalI != totalJ {
|
||||
return totalI > totalJ
|
||||
}
|
||||
if out[i].ProductCode != out[j].ProductCode {
|
||||
return out[i].ProductCode < out[j].ProductCode
|
||||
}
|
||||
if out[i].ColorCode != out[j].ColorCode {
|
||||
return out[i].ColorCode < out[j].ColorCode
|
||||
}
|
||||
return out[i].Dim3Code < out[j].Dim3Code
|
||||
})
|
||||
|
||||
writeJSON(w, productSeriesMappingsResponse{
|
||||
Rows: out,
|
||||
SizeColumns: sortSizeColumns(setToSortedSlice(sizeSet)),
|
||||
Definitions: defs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func productSeriesTotalQtyByCode(rows []productSeriesMappingRow) map[string]float64 {
|
||||
out := make(map[string]float64, len(rows))
|
||||
for _, row := range rows {
|
||||
out[row.ProductCode] += row.TotalQty
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type saveProductSeriesMappingsRequest struct {
|
||||
Items []struct {
|
||||
ProductCode string `json:"product_code"`
|
||||
ColorCode string `json:"color_code"`
|
||||
Dim3Code string `json:"dim3_code"`
|
||||
SeriesIDs []int64 `json:"series_ids"`
|
||||
} `json:"items"`
|
||||
}
|
||||
|
||||
func PostProductSeriesMappingsSaveHandler(pg *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req saveProductSeriesMappingsRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Gecersiz istek", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
tx, err := pg.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
http.Error(w, "Transaction baslatilamadi", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
saved := 0
|
||||
for _, item := range req.Items {
|
||||
code := strings.TrimSpace(item.ProductCode)
|
||||
color := strings.TrimSpace(item.ColorCode)
|
||||
dim3Token := strings.TrimSpace(item.Dim3Code)
|
||||
if code == "" || color == "" {
|
||||
continue
|
||||
}
|
||||
mmitemID, err := resolveMmitemIDTx(ctx, tx, code)
|
||||
if err != nil || mmitemID <= 0 {
|
||||
http.Error(w, "PG urun bulunamadi: "+code, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
dim1ID, err := resolveDimTokenIDTx(ctx, tx, "dimval1", color)
|
||||
if err != nil || dim1ID <= 0 {
|
||||
http.Error(w, "Renk token eslesmesi bulunamadi: "+color, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var dim3ID sql.NullInt64
|
||||
if dim3Token != "" {
|
||||
id, err := resolveDimTokenIDTx(ctx, tx, "dimval3", dim3Token)
|
||||
if err != nil || id <= 0 {
|
||||
http.Error(w, "Dim3 token eslesmesi bulunamadi: "+dim3Token, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
dim3ID = sql.NullInt64{Int64: id, Valid: true}
|
||||
}
|
||||
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
DELETE FROM zbggseri
|
||||
WHERE mmitem_id=$1
|
||||
AND dim1=$2
|
||||
AND (($3::bigint IS NULL AND dim3 IS NULL) OR dim3=$3::bigint)
|
||||
`, mmitemID, dim1ID, nullableInt64Arg(dim3ID)); err != nil {
|
||||
http.Error(w, "Seri eslesmesi temizlenemedi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
seen := map[int64]struct{}{}
|
||||
for _, seriesID := range item.SeriesIDs {
|
||||
if seriesID <= 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[seriesID]; ok {
|
||||
continue
|
||||
}
|
||||
seen[seriesID] = struct{}{}
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO zbggseri (mmitem_id, dim1, seri_id, dim3)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
`, mmitemID, dim1ID, seriesID, nullableInt64Arg(dim3ID)); err != nil {
|
||||
http.Error(w, "Seri eslesmesi kaydedilemedi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
saved++
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
http.Error(w, "Seri eslesmeleri kaydedilemedi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]any{"saved": saved})
|
||||
}
|
||||
}
|
||||
|
||||
func listProductSeriesDefinitions(ctx context.Context, pg *sql.DB, activeOnly bool) ([]productSeriesDefinition, error) {
|
||||
whereActive := ""
|
||||
if activeOnly {
|
||||
whereActive = " AND is_active=TRUE"
|
||||
}
|
||||
rows, err := pg.QueryContext(ctx, `
|
||||
SELECT id, COALESCE(code,''), COALESCE(title,''), COALESCE(is_active, TRUE),
|
||||
COALESCE(parent_filter,''), COALESCE(sort_order, 0), COALESCE(notes,'')
|
||||
FROM dfgrp
|
||||
WHERE master='zbggseri'`+whereActive+`
|
||||
ORDER BY sort_order,
|
||||
CASE WHEN code ~ '^[0-9]+$' THEN code::int ELSE NULL END NULLS LAST,
|
||||
code,
|
||||
id
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
out := []productSeriesDefinition{}
|
||||
for rows.Next() {
|
||||
var item productSeriesDefinition
|
||||
if err := rows.Scan(&item.ID, &item.Code, &item.Title, &item.IsActive, &item.ParentFilter, &item.SortOrder, &item.Notes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, item)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func loadMmitemIDs(ctx context.Context, pg *sql.DB, codes []string) (map[string]int64, error) {
|
||||
out := map[string]int64{}
|
||||
if len(codes) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
rows, err := pg.QueryContext(ctx, `SELECT code, id FROM mmitem WHERE code = ANY($1)`, pq.Array(codes))
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var code string
|
||||
var id int64
|
||||
if err := rows.Scan(&code, &id); err != nil {
|
||||
return out, err
|
||||
}
|
||||
out[strings.TrimSpace(code)] = id
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func loadDimTokenIDs(ctx context.Context, pg *sql.DB, column string, tokens []string) (map[string]int64, error) {
|
||||
out := map[string]int64{}
|
||||
if len(tokens) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
rows, err := pg.QueryContext(ctx, `
|
||||
SELECT token, dim_id
|
||||
FROM mk_dim_token_map
|
||||
WHERE dim_column=$1 AND token = ANY($2)
|
||||
`, column, pq.Array(tokens))
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var token string
|
||||
var id int64
|
||||
if err := rows.Scan(&token, &id); err != nil {
|
||||
return out, err
|
||||
}
|
||||
out[strings.TrimSpace(token)] = id
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func loadProductSeriesAssignments(ctx context.Context, pg *sql.DB, codes []string) (map[string][]int64, error) {
|
||||
out := map[string][]int64{}
|
||||
if len(codes) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
rows, err := pg.QueryContext(ctx, `
|
||||
SELECT m.code, z.dim1, COALESCE(z.dim3, 0), z.seri_id
|
||||
FROM zbggseri z
|
||||
JOIN mmitem m ON m.id = z.mmitem_id
|
||||
WHERE m.code = ANY($1)
|
||||
ORDER BY m.code, z.dim1, z.dim3, z.seri_id
|
||||
`, pq.Array(codes))
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var code string
|
||||
var dim1, dim3, seriesID int64
|
||||
if err := rows.Scan(&code, &dim1, &dim3, &seriesID); err != nil {
|
||||
return out, err
|
||||
}
|
||||
key := assignmentKey(strings.TrimSpace(code), dim1, dim3)
|
||||
out[key] = append(out[key], seriesID)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func resolveMmitemIDTx(ctx context.Context, tx *sql.Tx, code string) (int64, error) {
|
||||
var id int64
|
||||
err := tx.QueryRowContext(ctx, `SELECT id FROM mmitem WHERE code=$1`, code).Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
func resolveDimTokenIDTx(ctx context.Context, tx *sql.Tx, column string, token string) (int64, error) {
|
||||
var id int64
|
||||
err := tx.QueryRowContext(ctx, `SELECT dim_id FROM mk_dim_token_map WHERE dim_column=$1 AND token=$2`, column, token).Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
func nullableInt64Arg(v sql.NullInt64) any {
|
||||
if !v.Valid {
|
||||
return nil
|
||||
}
|
||||
return v.Int64
|
||||
}
|
||||
|
||||
func productSeriesKey(productCode, colorCode, dim3Code string) string {
|
||||
return strings.TrimSpace(productCode) + "|" + strings.TrimSpace(colorCode) + "|" + strings.TrimSpace(dim3Code)
|
||||
}
|
||||
|
||||
func assignmentKey(productCode string, dim1ID int64, dim3ID int64) string {
|
||||
return fmt.Sprintf("%s|%d|%d", strings.TrimSpace(productCode), dim1ID, dim3ID)
|
||||
}
|
||||
|
||||
func setToSortedSlice(set map[string]struct{}) []string {
|
||||
out := make([]string, 0, len(set))
|
||||
for v := range set {
|
||||
v = strings.TrimSpace(v)
|
||||
if v != "" {
|
||||
out = append(out, v)
|
||||
}
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
func sortSizeColumns(values []string) []string {
|
||||
sort.Slice(values, func(i, j int) bool {
|
||||
ai, ae := strconv.Atoi(values[i])
|
||||
bi, be := strconv.Atoi(values[j])
|
||||
if ae == nil && be == nil {
|
||||
return ai < bi
|
||||
}
|
||||
return values[i] < values[j]
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, payload any) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
_ = json.NewEncoder(w).Encode(payload)
|
||||
}
|
||||
Reference in New Issue
Block a user