Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-06-02 16:14:54 +03:00
parent 5f3e975b6d
commit b4e87cfd47
25 changed files with 4918 additions and 287 deletions

View File

@@ -55,8 +55,30 @@ func GetProductPricingListHandler(w http.ResponseWriter, r *http.Request) {
Karisim: splitCSVParam(r.URL.Query().Get("karisim")),
Marka: splitCSVParam(r.URL.Query().Get("marka")),
}
if len(filters.UrunAnaGrubu) > 3 {
http.Error(w, "Urun Ana Grubu en fazla 3 secilebilir", http.StatusBadRequest)
return
}
includeTotal := true
if raw := strings.TrimSpace(r.URL.Query().Get("include_total")); raw != "" {
if raw == "0" || strings.EqualFold(raw, "false") {
includeTotal = false
}
}
// When primary group filters are present, COUNT(*) is acceptable and improves UX
// (accurate totalCount/totalPages). Force includeTotal on.
if len(filters.UrunIlkGrubu) > 0 || len(filters.UrunAnaGrubu) > 0 {
includeTotal = true
}
pageResult, err := queries.GetProductPricingPage(ctx, page, limit, filters)
sortBy := strings.TrimSpace(r.URL.Query().Get("sort_by"))
desc := true
if raw := strings.TrimSpace(r.URL.Query().Get("desc")); raw != "" {
if raw == "0" || strings.EqualFold(raw, "false") {
desc = false
}
}
pageResult, err := queries.GetProductPricingPage(ctx, page, limit, filters, includeTotal, sortBy, desc)
if err != nil {
if isPricingTimeoutLike(err, ctx.Err()) {
log.Printf(
@@ -101,6 +123,94 @@ func GetProductPricingListHandler(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(pageResult.Rows)
}
// GET /api/pricing/products/options?field=urunAnaGrubu&q=SER&limit=120
func GetProductPricingFilterOptionsHandler(w http.ResponseWriter, r *http.Request) {
started := time.Now()
traceID := buildPricingTraceID(r)
w.Header().Set("X-Trace-ID", traceID)
claims, ok := auth.GetClaimsFromContext(r.Context())
if !ok || claims == nil {
log.Printf("[ProductPricingOptions] trace=%s unauthorized method=%s path=%s", traceID, r.Method, r.URL.Path)
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
ctx, cancel := context.WithTimeout(r.Context(), 12*time.Second)
defer cancel()
field := strings.TrimSpace(r.URL.Query().Get("field"))
q := strings.TrimSpace(r.URL.Query().Get("q"))
scopeUrunIlkGrubu := splitCSVParam(r.URL.Query().Get("urun_ilk_grubu"))
limit := 120
if raw := strings.TrimSpace(r.URL.Query().Get("limit")); raw != "" {
if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 && parsed <= 200 {
limit = parsed
}
}
items, err := queries.GetProductPricingFilterOptions(ctx, field, q, limit, scopeUrunIlkGrubu)
if err != nil {
if isPricingTimeoutLike(err, ctx.Err()) {
log.Printf(
"[ProductPricingOptions] trace=%s timeout user=%s id=%d field=%s q=%s duration_ms=%d err=%v",
traceID,
claims.Username,
claims.ID,
field,
q,
time.Since(started).Milliseconds(),
err,
)
http.Error(w, "Urun fiyatlandirma filtre secenekleri zaman asimina ugradi", http.StatusGatewayTimeout)
return
}
log.Printf(
"[ProductPricingOptions] trace=%s query_error user=%s id=%d field=%s q=%s duration_ms=%d err=%v",
traceID,
claims.Username,
claims.ID,
field,
q,
time.Since(started).Milliseconds(),
err,
)
http.Error(w, "Filtre secenekleri alinamadi: "+err.Error(), http.StatusInternalServerError)
return
}
type optionItem struct {
Label string `json:"label"`
Value string `json:"value"`
}
resp := struct {
Field string `json:"field"`
Count int `json:"count"`
Items []optionItem `json:"items"`
}{
Field: field,
Count: len(items),
Items: make([]optionItem, 0, len(items)),
}
for _, v := range items {
resp.Items = append(resp.Items, optionItem{Label: v, Value: v})
}
log.Printf(
"[ProductPricingOptions] trace=%s success user=%s id=%d field=%s q=%s count=%d duration_ms=%d",
traceID,
claims.Username,
claims.ID,
field,
q,
len(items),
time.Since(started).Milliseconds(),
)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(resp)
}
func buildPricingTraceID(r *http.Request) string {
if r != nil {
if id := strings.TrimSpace(r.Header.Get("X-Request-ID")); id != "" {