604 lines
18 KiB
Go
604 lines
18 KiB
Go
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)
|
|
}
|