From ec21dfac704392c76c8d48792d94f61779ba2614 Mon Sep 17 00:00:00 2001 From: M_Kececi Date: Wed, 24 Jun 2026 20:35:55 +0300 Subject: [PATCH] Merge remote-tracking branch 'origin/master' --- svc/product_series_auto_scheduler.go | 5 +++ svc/routes/product_series.go | 63 +++++++++++++++++++++++----- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/svc/product_series_auto_scheduler.go b/svc/product_series_auto_scheduler.go index c39a73d..3fa98f6 100644 --- a/svc/product_series_auto_scheduler.go +++ b/svc/product_series_auto_scheduler.go @@ -709,6 +709,11 @@ func productSeriesApplyVariant(ctx context.Context, pg *sql.DB, v productSeriesA return 0, 0, err } 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, ` DELETE FROM zbggseri WHERE mmitem_id=$1 diff --git a/svc/routes/product_series.go b/svc/routes/product_series.go index 76eab0e..9e23a6a 100644 --- a/svc/routes/product_series.go +++ b/svc/routes/product_series.go @@ -449,12 +449,30 @@ func PostProductSeriesMappingsSaveHandler(pg *sql.DB) http.HandlerFunc { } 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 + skipped := 0 for _, item := range req.Items { code := strings.TrimSpace(item.ProductCode) color := strings.TrimSpace(item.ColorCode) dim3Token := strings.TrimSpace(item.Dim3Code) if code == "" || color == "" { + skipped++ continue } mmitemID, err := resolveMmitemIDTx(ctx, tx, code) @@ -477,6 +495,39 @@ func PostProductSeriesMappingsSaveHandler(pg *sql.DB) http.HandlerFunc { 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, ` DELETE FROM zbggseri WHERE mmitem_id=$1 @@ -486,15 +537,7 @@ WHERE mmitem_id=$1 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{}{} + for _, seriesID := range seriesIDs { if _, err := tx.ExecContext(ctx, ` INSERT INTO zbggseri (mmitem_id, dim1, seri_id, dim3) VALUES ($1, $2, $3, $4) @@ -510,7 +553,7 @@ VALUES ($1, $2, $3, $4) http.Error(w, "Seri eslesmeleri kaydedilemedi: "+err.Error(), http.StatusInternalServerError) return } - writeJSON(w, map[string]any{"saved": saved}) + writeJSON(w, map[string]any{"saved": saved, "skipped": skipped}) } }