Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -847,6 +847,11 @@ func main() {
|
|||||||
auditlog.Init(pgDB, 1000)
|
auditlog.Init(pgDB, 1000)
|
||||||
log.Println("🕵️ AuditLog sistemi başlatıldı (buffer=1000)")
|
log.Println("🕵️ AuditLog sistemi başlatıldı (buffer=1000)")
|
||||||
|
|
||||||
|
// -------------------------------------------------------
|
||||||
|
// 🚀 TRANSLATION QUERY PERFORMANCE INDEXES
|
||||||
|
// -------------------------------------------------------
|
||||||
|
routes.EnsureTranslationPerfIndexes(pgDB)
|
||||||
|
|
||||||
// -------------------------------------------------------
|
// -------------------------------------------------------
|
||||||
// ✉️ MAILER INIT
|
// ✉️ MAILER INIT
|
||||||
// -------------------------------------------------------
|
// -------------------------------------------------------
|
||||||
|
|||||||
@@ -5,12 +5,20 @@ import (
|
|||||||
"bssapp-backend/models"
|
"bssapp-backend/models"
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetProductPricingList(ctx context.Context) ([]models.ProductPricing, error) {
|
func GetProductPricingList(ctx context.Context, limit int, offset int) ([]models.ProductPricing, error) {
|
||||||
const query = `
|
if limit <= 0 {
|
||||||
|
limit = 500
|
||||||
|
}
|
||||||
|
if offset < 0 {
|
||||||
|
offset = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `
|
||||||
WITH base_products AS (
|
WITH base_products AS (
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(ProductCode)) AS ProductCode,
|
LTRIM(RTRIM(ProductCode)) AS ProductCode,
|
||||||
@@ -27,6 +35,13 @@ func GetProductPricingList(ctx context.Context) ([]models.ProductPricing, error)
|
|||||||
AND IsBlocked = 0
|
AND IsBlocked = 0
|
||||||
AND LEN(LTRIM(RTRIM(ProductCode))) = 13
|
AND LEN(LTRIM(RTRIM(ProductCode))) = 13
|
||||||
),
|
),
|
||||||
|
paged_products AS (
|
||||||
|
SELECT
|
||||||
|
bp.ProductCode
|
||||||
|
FROM base_products bp
|
||||||
|
ORDER BY bp.ProductCode
|
||||||
|
OFFSET %d ROWS FETCH NEXT %d ROWS ONLY
|
||||||
|
),
|
||||||
latest_base_price AS (
|
latest_base_price AS (
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(b.ItemCode)) AS ItemCode,
|
LTRIM(RTRIM(b.ItemCode)) AS ItemCode,
|
||||||
@@ -42,8 +57,8 @@ func GetProductPricingList(ctx context.Context) ([]models.ProductPricing, error)
|
|||||||
AND LTRIM(RTRIM(b.CurrencyCode)) = 'USD'
|
AND LTRIM(RTRIM(b.CurrencyCode)) = 'USD'
|
||||||
AND EXISTS (
|
AND EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM base_products bp
|
FROM paged_products pp
|
||||||
WHERE bp.ProductCode = LTRIM(RTRIM(b.ItemCode))
|
WHERE pp.ProductCode = LTRIM(RTRIM(b.ItemCode))
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
stock_entry_dates AS (
|
stock_entry_dates AS (
|
||||||
@@ -61,8 +76,8 @@ func GetProductPricingList(ctx context.Context) ([]models.ProductPricing, error)
|
|||||||
)
|
)
|
||||||
AND EXISTS (
|
AND EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM base_products bp
|
FROM paged_products pp
|
||||||
WHERE bp.ProductCode = LTRIM(RTRIM(s.ItemCode))
|
WHERE pp.ProductCode = LTRIM(RTRIM(s.ItemCode))
|
||||||
)
|
)
|
||||||
GROUP BY LTRIM(RTRIM(s.ItemCode))
|
GROUP BY LTRIM(RTRIM(s.ItemCode))
|
||||||
),
|
),
|
||||||
@@ -75,8 +90,8 @@ func GetProductPricingList(ctx context.Context) ([]models.ProductPricing, error)
|
|||||||
AND LEN(LTRIM(RTRIM(s.ItemCode))) = 13
|
AND LEN(LTRIM(RTRIM(s.ItemCode))) = 13
|
||||||
AND EXISTS (
|
AND EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM base_products bp
|
FROM paged_products pp
|
||||||
WHERE bp.ProductCode = LTRIM(RTRIM(s.ItemCode))
|
WHERE pp.ProductCode = LTRIM(RTRIM(s.ItemCode))
|
||||||
)
|
)
|
||||||
GROUP BY LTRIM(RTRIM(s.ItemCode))
|
GROUP BY LTRIM(RTRIM(s.ItemCode))
|
||||||
),
|
),
|
||||||
@@ -89,8 +104,8 @@ func GetProductPricingList(ctx context.Context) ([]models.ProductPricing, error)
|
|||||||
AND LEN(LTRIM(RTRIM(p.ItemCode))) = 13
|
AND LEN(LTRIM(RTRIM(p.ItemCode))) = 13
|
||||||
AND EXISTS (
|
AND EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM base_products bp
|
FROM paged_products pp
|
||||||
WHERE bp.ProductCode = LTRIM(RTRIM(p.ItemCode))
|
WHERE pp.ProductCode = LTRIM(RTRIM(p.ItemCode))
|
||||||
)
|
)
|
||||||
GROUP BY LTRIM(RTRIM(p.ItemCode))
|
GROUP BY LTRIM(RTRIM(p.ItemCode))
|
||||||
),
|
),
|
||||||
@@ -103,8 +118,8 @@ func GetProductPricingList(ctx context.Context) ([]models.ProductPricing, error)
|
|||||||
AND LEN(LTRIM(RTRIM(r.ItemCode))) = 13
|
AND LEN(LTRIM(RTRIM(r.ItemCode))) = 13
|
||||||
AND EXISTS (
|
AND EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM base_products bp
|
FROM paged_products pp
|
||||||
WHERE bp.ProductCode = LTRIM(RTRIM(r.ItemCode))
|
WHERE pp.ProductCode = LTRIM(RTRIM(r.ItemCode))
|
||||||
)
|
)
|
||||||
GROUP BY LTRIM(RTRIM(r.ItemCode))
|
GROUP BY LTRIM(RTRIM(r.ItemCode))
|
||||||
),
|
),
|
||||||
@@ -117,29 +132,29 @@ func GetProductPricingList(ctx context.Context) ([]models.ProductPricing, error)
|
|||||||
AND LEN(LTRIM(RTRIM(d.ItemCode))) = 13
|
AND LEN(LTRIM(RTRIM(d.ItemCode))) = 13
|
||||||
AND EXISTS (
|
AND EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM base_products bp
|
FROM paged_products pp
|
||||||
WHERE bp.ProductCode = LTRIM(RTRIM(d.ItemCode))
|
WHERE pp.ProductCode = LTRIM(RTRIM(d.ItemCode))
|
||||||
)
|
)
|
||||||
GROUP BY LTRIM(RTRIM(d.ItemCode))
|
GROUP BY LTRIM(RTRIM(d.ItemCode))
|
||||||
),
|
),
|
||||||
stock_totals AS (
|
stock_totals AS (
|
||||||
SELECT
|
SELECT
|
||||||
bp.ProductCode AS ItemCode,
|
pp.ProductCode AS ItemCode,
|
||||||
CAST(ROUND(
|
CAST(ROUND(
|
||||||
ISNULL(sb.InventoryQty1, 0)
|
ISNULL(sb.InventoryQty1, 0)
|
||||||
- ISNULL(pb.PickingQty1, 0)
|
- ISNULL(pb.PickingQty1, 0)
|
||||||
- ISNULL(rb.ReserveQty1, 0)
|
- ISNULL(rb.ReserveQty1, 0)
|
||||||
- ISNULL(db.DispOrderQty1, 0)
|
- ISNULL(db.DispOrderQty1, 0)
|
||||||
, 2) AS DECIMAL(18, 2)) AS StockQty
|
, 2) AS DECIMAL(18, 2)) AS StockQty
|
||||||
FROM base_products bp
|
FROM paged_products pp
|
||||||
LEFT JOIN stock_base sb
|
LEFT JOIN stock_base sb
|
||||||
ON sb.ItemCode = bp.ProductCode
|
ON sb.ItemCode = pp.ProductCode
|
||||||
LEFT JOIN pick_base pb
|
LEFT JOIN pick_base pb
|
||||||
ON pb.ItemCode = bp.ProductCode
|
ON pb.ItemCode = pp.ProductCode
|
||||||
LEFT JOIN reserve_base rb
|
LEFT JOIN reserve_base rb
|
||||||
ON rb.ItemCode = bp.ProductCode
|
ON rb.ItemCode = pp.ProductCode
|
||||||
LEFT JOIN disp_base db
|
LEFT JOIN disp_base db
|
||||||
ON db.ItemCode = bp.ProductCode
|
ON db.ItemCode = pp.ProductCode
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
bp.ProductCode AS ProductCode,
|
bp.ProductCode AS ProductCode,
|
||||||
@@ -155,7 +170,9 @@ func GetProductPricingList(ctx context.Context) ([]models.ProductPricing, error)
|
|||||||
bp.Icerik,
|
bp.Icerik,
|
||||||
bp.Karisim,
|
bp.Karisim,
|
||||||
bp.Marka
|
bp.Marka
|
||||||
FROM base_products bp
|
FROM paged_products pp
|
||||||
|
INNER JOIN base_products bp
|
||||||
|
ON bp.ProductCode = pp.ProductCode
|
||||||
LEFT JOIN latest_base_price lp
|
LEFT JOIN latest_base_price lp
|
||||||
ON lp.ItemCode = bp.ProductCode
|
ON lp.ItemCode = bp.ProductCode
|
||||||
AND lp.rn = 1
|
AND lp.rn = 1
|
||||||
@@ -165,6 +182,7 @@ func GetProductPricingList(ctx context.Context) ([]models.ProductPricing, error)
|
|||||||
ON st.ItemCode = bp.ProductCode
|
ON st.ItemCode = bp.ProductCode
|
||||||
ORDER BY bp.ProductCode;
|
ORDER BY bp.ProductCode;
|
||||||
`
|
`
|
||||||
|
query = fmt.Sprintf(query, offset, limit)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rows *sql.Rows
|
rows *sql.Rows
|
||||||
|
|||||||
@@ -29,7 +29,20 @@ func GetProductPricingListHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
ctx, cancel := context.WithTimeout(r.Context(), 180*time.Second)
|
ctx, cancel := context.WithTimeout(r.Context(), 180*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
rows, err := queries.GetProductPricingList(ctx)
|
limit := 500
|
||||||
|
if raw := strings.TrimSpace(r.URL.Query().Get("limit")); raw != "" {
|
||||||
|
if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 && parsed <= 10000 {
|
||||||
|
limit = parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset := 0
|
||||||
|
if raw := strings.TrimSpace(r.URL.Query().Get("offset")); raw != "" {
|
||||||
|
if parsed, err := strconv.Atoi(raw); err == nil && parsed >= 0 && parsed <= 1000000 {
|
||||||
|
offset = parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := queries.GetProductPricingList(ctx, limit+1, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isPricingTimeoutLike(err, ctx.Err()) {
|
if isPricingTimeoutLike(err, ctx.Err()) {
|
||||||
log.Printf(
|
log.Printf(
|
||||||
@@ -54,16 +67,29 @@ func GetProductPricingListHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "Urun fiyatlandirma listesi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Urun fiyatlandirma listesi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
hasMore := len(rows) > limit
|
||||||
|
if hasMore {
|
||||||
|
rows = rows[:limit]
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"[ProductPricing] trace=%s success user=%s id=%d count=%d duration_ms=%d",
|
"[ProductPricing] trace=%s success user=%s id=%d limit=%d offset=%d count=%d has_more=%t duration_ms=%d",
|
||||||
traceID,
|
traceID,
|
||||||
claims.Username,
|
claims.Username,
|
||||||
claims.ID,
|
claims.ID,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
len(rows),
|
len(rows),
|
||||||
|
hasMore,
|
||||||
time.Since(started).Milliseconds(),
|
time.Since(started).Milliseconds(),
|
||||||
)
|
)
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
if hasMore {
|
||||||
|
w.Header().Set("X-Has-More", "true")
|
||||||
|
} else {
|
||||||
|
w.Header().Set("X-Has-More", "false")
|
||||||
|
}
|
||||||
_ = json.NewEncoder(w).Encode(rows)
|
_ = json.NewEncoder(w).Encode(rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
41
svc/routes/translation_perf.go
Normal file
41
svc/routes/translation_perf.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnsureTranslationPerfIndexes creates helpful indexes for translation listing/search.
|
||||||
|
// It is safe to run on each startup; failures are logged and do not stop the service.
|
||||||
|
func EnsureTranslationPerfIndexes(db *sql.DB) {
|
||||||
|
if db == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statements := []string{
|
||||||
|
`CREATE EXTENSION IF NOT EXISTS pg_trgm`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_mk_translator_t_key_lang ON mk_translator (t_key, lang_code)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_mk_translator_status_lang_updated ON mk_translator (status, lang_code, updated_at DESC)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_mk_translator_manual_status ON mk_translator (is_manual, status)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_mk_translator_source_type_expr ON mk_translator ((COALESCE(NULLIF(provider_meta->>'source_type',''),'dummy')))`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_mk_translator_source_text_trgm ON mk_translator USING gin (source_text_tr gin_trgm_ops)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_mk_translator_translated_text_trgm ON mk_translator USING gin (translated_text gin_trgm_ops)`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stmt := range statements {
|
||||||
|
if _, err := db.Exec(stmt); err != nil {
|
||||||
|
log.Printf("[TranslationPerf] index_setup_warn sql=%q err=%v", summarizeSQL(stmt), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Printf("[TranslationPerf] index_ready sql=%q", summarizeSQL(stmt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func summarizeSQL(sqlText string) string {
|
||||||
|
s := strings.TrimSpace(sqlText)
|
||||||
|
| ||||||