Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -709,6 +709,11 @@ func productSeriesApplyVariant(ctx context.Context, pg *sql.DB, v productSeriesA
|
|||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// Tag the DB session for audit attribution (scheduler vs http handler).
|
||||||
|
// This affects only statements in this transaction.
|
||||||
|
_, _ = tx.ExecContext(ctx, `SELECT set_config('application_name', current_setting('application_name') || '|series-auto', true)`)
|
||||||
|
|
||||||
if _, err := tx.ExecContext(ctx, `
|
if _, err := tx.ExecContext(ctx, `
|
||||||
DELETE FROM zbggseri
|
DELETE FROM zbggseri
|
||||||
WHERE mmitem_id=$1
|
WHERE mmitem_id=$1
|
||||||
|
|||||||
@@ -449,12 +449,30 @@ func PostProductSeriesMappingsSaveHandler(pg *sql.DB) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// Tag the DB session for audit attribution (http handler vs scheduler).
|
||||||
|
// This affects only statements in this transaction.
|
||||||
|
_, _ = tx.ExecContext(ctx, `SELECT set_config('application_name', current_setting('application_name') || '|series-save', true)`)
|
||||||
|
|
||||||
|
// Safety defaults:
|
||||||
|
// - Don't allow accidental clearing when UI sends empty series_ids.
|
||||||
|
// - Don't allow saving fallback code=1 from UI unless explicitly enabled.
|
||||||
|
allowClear := strings.TrimSpace(strings.ToLower(r.URL.Query().Get("clear"))) == "true"
|
||||||
|
allowFallback := strings.TrimSpace(strings.ToLower(r.URL.Query().Get("allow_fallback"))) == "true"
|
||||||
|
|
||||||
|
log.Printf("[ProductSeriesMappingsSave] items=%d allow_clear=%t allow_fallback=%t remote=%s ua=%s",
|
||||||
|
len(req.Items), allowClear, allowFallback, strings.TrimSpace(r.RemoteAddr), strings.TrimSpace(r.UserAgent()))
|
||||||
|
|
||||||
|
var fallbackSeriesID int64 = 0
|
||||||
|
_ = tx.QueryRowContext(ctx, `SELECT id FROM dfgrp WHERE master='zbggseri' AND code='1'`).Scan(&fallbackSeriesID)
|
||||||
|
|
||||||
saved := 0
|
saved := 0
|
||||||
|
skipped := 0
|
||||||
for _, item := range req.Items {
|
for _, item := range req.Items {
|
||||||
code := strings.TrimSpace(item.ProductCode)
|
code := strings.TrimSpace(item.ProductCode)
|
||||||
color := strings.TrimSpace(item.ColorCode)
|
color := strings.TrimSpace(item.ColorCode)
|
||||||
dim3Token := strings.TrimSpace(item.Dim3Code)
|
dim3Token := strings.TrimSpace(item.Dim3Code)
|
||||||
if code == "" || color == "" {
|
if code == "" || color == "" {
|
||||||
|
skipped++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mmitemID, err := resolveMmitemIDTx(ctx, tx, code)
|
mmitemID, err := resolveMmitemIDTx(ctx, tx, code)
|
||||||
@@ -477,6 +495,39 @@ func PostProductSeriesMappingsSaveHandler(pg *sql.DB) http.HandlerFunc {
|
|||||||
dim3ID = sql.NullInt64{Int64: id, Valid: true}
|
dim3ID = sql.NullInt64{Int64: id, Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize/validate series ids.
|
||||||
|
seen := map[int64]struct{}{}
|
||||||
|
seriesIDs := make([]int64, 0, len(item.SeriesIDs))
|
||||||
|
for _, seriesID := range item.SeriesIDs {
|
||||||
|
if seriesID <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fallbackSeriesID > 0 && seriesID == fallbackSeriesID && !allowFallback {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seen[seriesID]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[seriesID] = struct{}{}
|
||||||
|
seriesIDs = append(seriesIDs, seriesID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent accidental destructive writes from UI.
|
||||||
|
if len(seriesIDs) == 0 && !allowClear {
|
||||||
|
log.Printf("[ProductSeriesMappingsSave] skip_empty_series_ids product=%s color=%s dim3=%s", code, color, dim3Token)
|
||||||
|
skipped++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
out := make([]string, 0, len(seriesIDs))
|
||||||
|
for _, id := range seriesIDs {
|
||||||
|
out = append(out, fmt.Sprintf("%d", id))
|
||||||
|
}
|
||||||
|
log.Printf("[ProductSeriesMappingsSave] write product=%s color=%s dim3=%s series_ids=%s",
|
||||||
|
code, color, dim3Token, strings.Join(out, ","))
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := tx.ExecContext(ctx, `
|
if _, err := tx.ExecContext(ctx, `
|
||||||
DELETE FROM zbggseri
|
DELETE FROM zbggseri
|
||||||
WHERE mmitem_id=$1
|
WHERE mmitem_id=$1
|
||||||
@@ -486,15 +537,7 @@ WHERE mmitem_id=$1
|
|||||||
http.Error(w, "Seri eslesmesi temizlenemedi: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Seri eslesmesi temizlenemedi: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
seen := map[int64]struct{}{}
|
for _, seriesID := range seriesIDs {
|
||||||
for _, seriesID := range item.SeriesIDs {
|
|
||||||
if seriesID <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := seen[seriesID]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[seriesID] = struct{}{}
|
|
||||||
if _, err := tx.ExecContext(ctx, `
|
if _, err := tx.ExecContext(ctx, `
|
||||||
INSERT INTO zbggseri (mmitem_id, dim1, seri_id, dim3)
|
INSERT INTO zbggseri (mmitem_id, dim1, seri_id, dim3)
|
||||||
VALUES ($1, $2, $3, $4)
|
VALUES ($1, $2, $3, $4)
|
||||||
@@ -510,7 +553,7 @@ VALUES ($1, $2, $3, $4)
|
|||||||
http.Error(w, "Seri eslesmeleri kaydedilemedi: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Seri eslesmeleri kaydedilemedi: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeJSON(w, map[string]any{"saved": saved})
|
writeJSON(w, map[string]any{"saved": saved, "skipped": skipped})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user