2064 lines
66 KiB
Go
2064 lines
66 KiB
Go
package routes
|
||
|
||
import (
|
||
"bssapp-backend/auth"
|
||
"bssapp-backend/db"
|
||
"bssapp-backend/models"
|
||
"bssapp-backend/queries"
|
||
"bssapp-backend/utils"
|
||
"context"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"log"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
func logProductionHasCostDetailEditorOptionItemDiagnostics(ctx context.Context, uretimDB *sql.DB, search string, limit int) {
|
||
logger := utils.SlogFromContext(ctx).With(
|
||
"handler", "production-product-costing.detail-editor-options.diagnostics",
|
||
"search", search,
|
||
"limit", limit,
|
||
)
|
||
expectedColumns := []string{
|
||
"nStokID",
|
||
"sKodu",
|
||
"sAciklama",
|
||
"sModel",
|
||
"sBirimCinsi1",
|
||
"IsBlocked",
|
||
}
|
||
probes := []struct {
|
||
name string
|
||
sqlText string
|
||
}{
|
||
{
|
||
name: "projection:nStokID",
|
||
sqlText: `SELECT TOP 1 RTRIM(CONVERT(VARCHAR(32), ISNULL(S.nStokID, 0))) FROM dbo.tbStok S`,
|
||
},
|
||
{
|
||
name: "projection:sKodu",
|
||
sqlText: `SELECT TOP 1 LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sKodu, '')))) FROM dbo.tbStok S`,
|
||
},
|
||
{
|
||
name: "projection:sAciklama",
|
||
sqlText: `SELECT TOP 1 LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sAciklama, '')))) FROM dbo.tbStok S`,
|
||
},
|
||
{
|
||
name: "projection:sModel",
|
||
sqlText: `SELECT TOP 1 LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sModel, '')))) FROM dbo.tbStok S`,
|
||
},
|
||
{
|
||
name: "projection:sBirimCinsi1",
|
||
sqlText: `SELECT TOP 1 LTRIM(RTRIM(CONVERT(NVARCHAR(64), ISNULL(S.sBirimCinsi1, '')))) FROM dbo.tbStok S`,
|
||
},
|
||
{
|
||
name: "projection:IsBlocked",
|
||
sqlText: `SELECT TOP 1 RTRIM(CONVERT(VARCHAR(32), ISNULL(S.IsBlocked, 0))) FROM dbo.tbStok S`,
|
||
},
|
||
{
|
||
name: "filter:model_like_count",
|
||
sqlText: `SELECT RTRIM(CONVERT(VARCHAR(32), COUNT(1))) FROM dbo.tbStok S WHERE LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sModel, '')))) LIKE '_.%'`,
|
||
},
|
||
{
|
||
name: "filter:isblocked_count",
|
||
sqlText: `SELECT RTRIM(CONVERT(VARCHAR(32), COUNT(1))) FROM dbo.tbStok S WHERE ISNULL(S.IsBlocked, 0) = 0`,
|
||
},
|
||
{
|
||
name: "filter:final_count",
|
||
sqlText: `SELECT RTRIM(CONVERT(VARCHAR(32), COUNT(1))) FROM dbo.tbStok S WHERE ISNULL(S.IsBlocked, 0) = 0 AND LTRIM(RTRIM(CONVERT(NVARCHAR(255), ISNULL(S.sModel, '')))) LIKE '_.%'`,
|
||
},
|
||
}
|
||
|
||
log.Printf("[ProductionHasCostDetailEditorOptions] item diagnostics start search=%q limit=%d", search, limit)
|
||
|
||
for _, columnName := range expectedColumns {
|
||
var exists int
|
||
err := uretimDB.QueryRowContext(
|
||
ctx,
|
||
`SELECT COUNT(1)
|
||
FROM INFORMATION_SCHEMA.COLUMNS
|
||
WHERE TABLE_SCHEMA = 'dbo'
|
||
AND TABLE_NAME = 'tbStok'
|
||
AND COLUMN_NAME = @p1`,
|
||
columnName,
|
||
).Scan(&exists)
|
||
if err != nil {
|
||
logger.Error("query error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] item diagnostics column check error column=%s err=%v", columnName, err)
|
||
continue
|
||
}
|
||
log.Printf("[ProductionHasCostDetailEditorOptions] item diagnostics column=%s exists=%t", columnName, exists > 0)
|
||
}
|
||
|
||
for _, probe := range probes {
|
||
var sample sql.NullString
|
||
err := uretimDB.QueryRowContext(ctx, probe.sqlText).Scan(&sample)
|
||
if err != nil {
|
||
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] item diagnostics probe=%s err=%v", probe.name, err)
|
||
continue
|
||
}
|
||
log.Printf("[ProductionHasCostDetailEditorOptions] item diagnostics probe=%s sample=%q", probe.name, strings.TrimSpace(sample.String))
|
||
}
|
||
|
||
log.Printf("[ProductionHasCostDetailEditorOptions] item diagnostics end search=%q limit=%d", search, limit)
|
||
}
|
||
|
||
// GET /api/pricing/production-product-costing/no-cost-products
|
||
func GetProductionNoCostProductsHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
uretimDB := db.GetUretimDB()
|
||
if uretimDB == nil {
|
||
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
search := strings.TrimSpace(r.URL.Query().Get("search"))
|
||
fromDate := strings.TrimSpace(r.URL.Query().Get("from_date"))
|
||
|
||
rows, err := queries.GetProductionNoCostProducts(r.Context(), uretimDB, fromDate, search)
|
||
if err != nil {
|
||
log.Printf("❌ [ProductionNoCost] query error: %v", err)
|
||
http.Error(w, "Veritabanı hatası", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
list := make([]models.ProductionNoCostProductRow, 0, 200)
|
||
for rows.Next() {
|
||
var item models.ProductionNoCostProductRow
|
||
if err := rows.Scan(
|
||
&item.UretimSekli,
|
||
&item.UrtSiparisNo,
|
||
&item.IslemTarihi,
|
||
&item.FirmaKodu,
|
||
&item.FirmaAdi,
|
||
&item.SonIsEmriVeren,
|
||
&item.MMiktarG,
|
||
&item.MModelKodu,
|
||
&item.Kodu,
|
||
&item.ModelAdi,
|
||
&item.SKullaniciAdi,
|
||
&item.SKullaniciGunc,
|
||
); err != nil {
|
||
log.Printf("⚠️ [ProductionNoCost] scan error: %v", err)
|
||
continue
|
||
}
|
||
list = append(list, item)
|
||
}
|
||
|
||
if err := rows.Err(); err != nil {
|
||
log.Printf("⚠️ [ProductionNoCost] rows error: %v", err)
|
||
http.Error(w, "Veritabanı satır hatası", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
|
||
// GET /api/pricing/production-product-costing/has-cost-products
|
||
func GetProductionHasCostProductsHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
uretimDB := db.GetUretimDB()
|
||
if uretimDB == nil {
|
||
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
search := strings.TrimSpace(r.URL.Query().Get("search"))
|
||
offset := parsePositiveIntOrDefault(r.URL.Query().Get("offset"), 0)
|
||
limit := parsePositiveIntOrDefault(r.URL.Query().Get("limit"), 300)
|
||
if limit > 1000 {
|
||
limit = 1000
|
||
}
|
||
|
||
rows, err := queries.GetProductionHasCostProducts(r.Context(), uretimDB, search, offset, limit)
|
||
if err != nil {
|
||
log.Printf("❌ [ProductionHasCost] query error: %v", err)
|
||
http.Error(w, "Veritabanı hatası", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
list := make([]models.ProductionHasCostProductRow, 0, 200)
|
||
for rows.Next() {
|
||
var item models.ProductionHasCostProductRow
|
||
if err := rows.Scan(
|
||
&item.UretimSekli,
|
||
&item.NOnMLNo,
|
||
&item.UrunKodu,
|
||
&item.UrunAdi,
|
||
&item.Tarihi,
|
||
&item.DteKayitTarihi,
|
||
&item.SKullaniciAdi,
|
||
&item.LTutarTL,
|
||
&item.LTutarUSD,
|
||
&item.LTutarEURO,
|
||
&item.DteGuncellemeTarihi,
|
||
&item.SGuncellemeKullaniciAdi,
|
||
&item.NUrtReceteID,
|
||
&item.SAciklama,
|
||
&item.SonSiparisTarihi,
|
||
&item.MaliyetDurumu,
|
||
); err != nil {
|
||
log.Printf("⚠️ [ProductionHasCost] scan error: %v", err)
|
||
continue
|
||
}
|
||
list = append(list, item)
|
||
}
|
||
|
||
if err := rows.Err(); err != nil {
|
||
log.Printf("⚠️ [ProductionHasCost] rows error: %v", err)
|
||
http.Error(w, "Veritabanı satır hatası", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
|
||
// GET /api/pricing/production-product-costing/has-cost-history
|
||
func GetProductionHasCostHistoryHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
uretimDB := db.GetUretimDB()
|
||
if uretimDB == nil {
|
||
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
productCode := strings.TrimSpace(r.URL.Query().Get("urun_kodu"))
|
||
if productCode == "" {
|
||
http.Error(w, "urun_kodu zorunlu", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
rows, err := queries.GetProductionHasCostHistoryByProductCode(r.Context(), uretimDB, productCode)
|
||
if err != nil {
|
||
log.Printf("⌠[ProductionHasCostHistory] query error: %v", err)
|
||
http.Error(w, "Veritabanı hatası", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
list := make([]models.ProductionHasCostHistoryRow, 0, 100)
|
||
for rows.Next() {
|
||
var item models.ProductionHasCostHistoryRow
|
||
if err := rows.Scan(
|
||
&item.NOnMLNo,
|
||
&item.UrunKodu,
|
||
&item.UrunAdi,
|
||
&item.Tarihi,
|
||
&item.SKullaniciAdi,
|
||
&item.LTutarUSD,
|
||
&item.LTutarTL,
|
||
&item.LTutarEURO,
|
||
&item.SDovizCinsi,
|
||
&item.LTutarDoviz,
|
||
&item.DteGuncellemeTarihi,
|
||
&item.SGuncellemeKullaniciAdi,
|
||
&item.NUrtReceteID,
|
||
&item.SAciklama,
|
||
); err != nil {
|
||
log.Printf("âš ï¸ [ProductionHasCostHistory] scan error: %v", err)
|
||
continue
|
||
}
|
||
list = append(list, item)
|
||
}
|
||
|
||
if err := rows.Err(); err != nil {
|
||
log.Printf("âš ï¸ [ProductionHasCostHistory] rows error: %v", err)
|
||
http.Error(w, "Veritabanı satır hatası", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
|
||
// GET /api/pricing/production-product-costing/has-cost-detail-groups
|
||
func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
uretimDB := db.GetUretimDB()
|
||
if uretimDB == nil {
|
||
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
detailSource := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("detail_source")))
|
||
recipeCode := strings.TrimSpace(r.URL.Query().Get("recete_kodu"))
|
||
productCode := strings.TrimSpace(r.URL.Query().Get("urun_kodu"))
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
logger := utils.SlogFromContext(ctx).With(
|
||
"handler", "production-product-costing.detail-groups",
|
||
"detail_source", detailSource,
|
||
"urun_kodu", productCode,
|
||
"recete_kodu", recipeCode,
|
||
)
|
||
|
||
if detailSource == "no-cost" || recipeCode != "" {
|
||
if recipeCode == "" {
|
||
logger.Warn("request invalid", "reason", "missing recete_kodu")
|
||
http.Error(w, "recete_kodu zorunlu", http.StatusBadRequest)
|
||
return
|
||
}
|
||
logger.Info("request start")
|
||
|
||
rows, err := queries.GetProductionNoCostDetailRowsByRecipeCode(ctx, uretimDB, recipeCode, productCode)
|
||
if err != nil {
|
||
logger.Error("query error", "err", err)
|
||
log.Printf("❌ [ProductionNoCostDetailGroups] query error: %v", err)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
groups := make([]models.ProductionHasCostDetailGroup, 0, 16)
|
||
groupIndexByName := map[string]int{}
|
||
scannedRows := 0
|
||
scanErrors := 0
|
||
|
||
for rows.Next() {
|
||
var (
|
||
groupName string
|
||
groupTotal float64
|
||
groupTotalUSD float64
|
||
fiyatGirilen sql.NullFloat64
|
||
fiyatDoviz sql.NullString
|
||
maliyeteDahil sql.NullBool
|
||
cmPriceTypeID sql.NullInt64
|
||
item models.ProductionHasCostDetailGroupItem
|
||
)
|
||
|
||
if err := rows.Scan(
|
||
&groupName,
|
||
&groupTotal,
|
||
&groupTotalUSD,
|
||
&item.NOnMLNo,
|
||
&item.NOnMLDetNo,
|
||
&item.NHammaddeTuruNo,
|
||
&item.SKodu,
|
||
&item.SAciklama,
|
||
&item.SRenk,
|
||
&item.SBeden,
|
||
&item.SAciklama2,
|
||
&item.LMiktar,
|
||
&item.LFiyat,
|
||
&item.LTutar,
|
||
&item.SFiyatTipi,
|
||
&item.SDovizCinsi,
|
||
&item.LDovizKuru,
|
||
&item.LDovizFiyati,
|
||
&fiyatGirilen,
|
||
&fiyatDoviz,
|
||
&maliyeteDahil,
|
||
&cmPriceTypeID,
|
||
&item.USDTutar,
|
||
&item.EURTutar,
|
||
&item.GBPTutar,
|
||
&item.SBirim,
|
||
&item.SHammaddeTuruAdi,
|
||
&item.SParcaAdi,
|
||
); err != nil {
|
||
scanErrors += 1
|
||
logger.Warn("scan error", "scan_index", scannedRows+scanErrors+1, "err", err)
|
||
log.Printf("⚠️ [ProductionNoCostDetailGroups] scan error: %v", err)
|
||
continue
|
||
}
|
||
scannedRows += 1
|
||
|
||
if fiyatGirilen.Valid {
|
||
item.FiyatGirilen = new(float64)
|
||
*item.FiyatGirilen = fiyatGirilen.Float64
|
||
}
|
||
if fiyatDoviz.Valid {
|
||
item.FiyatDoviz = strings.TrimSpace(fiyatDoviz.String)
|
||
}
|
||
item.MaliyeteDahil = !maliyeteDahil.Valid || maliyeteDahil.Bool
|
||
if cmPriceTypeID.Valid {
|
||
value := int(cmPriceTypeID.Int64)
|
||
item.CMPriceTypeID = &value
|
||
}
|
||
|
||
idx, ok := groupIndexByName[groupName]
|
||
if !ok {
|
||
groups = append(groups, models.ProductionHasCostDetailGroup{
|
||
SAciklama3: groupName,
|
||
TotalTutar: groupTotal,
|
||
TotalUSDTutar: groupTotalUSD,
|
||
Items: make([]models.ProductionHasCostDetailGroupItem, 0, 8),
|
||
})
|
||
idx = len(groups) - 1
|
||
groupIndexByName[groupName] = idx
|
||
}
|
||
|
||
groups[idx].Items = append(groups[idx].Items, item)
|
||
}
|
||
|
||
if err := rows.Err(); err != nil {
|
||
logger.Error("rows error", "err", err)
|
||
log.Printf("⚠️ [ProductionNoCostDetailGroups] rows error: %v", err)
|
||
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
logger.Info("request done", "group_count", len(groups), "row_count", scannedRows, "scan_errors", scanErrors)
|
||
log.Printf("[ProductionNoCostDetailGroups] done recete_kodu=%s groups=%d", recipeCode, len(groups))
|
||
_ = json.NewEncoder(w).Encode(groups)
|
||
return
|
||
}
|
||
|
||
rawOnMLNo := strings.TrimSpace(r.URL.Query().Get("n_onml_no"))
|
||
nOnMLNo, err := strconv.Atoi(rawOnMLNo)
|
||
if err != nil || nOnMLNo <= 0 {
|
||
logger.Warn("request invalid", "reason", "invalid n_onml_no", "raw_n_onml_no", rawOnMLNo)
|
||
http.Error(w, "n_onml_no zorunlu ve pozitif sayi olmali", http.StatusBadRequest)
|
||
return
|
||
}
|
||
logger = logger.With("n_onml_no", nOnMLNo)
|
||
logger.Info("request start")
|
||
log.Printf("[ProductionHasCostDetailGroups] start n_onml_no=%d", nOnMLNo)
|
||
|
||
rows, err := queries.GetProductionHasCostDetailRowsByOnMLNo(ctx, uretimDB, nOnMLNo)
|
||
if err != nil {
|
||
logger.Error("query error", "err", err)
|
||
log.Printf("⌠[ProductionHasCostDetailGroups] query error: %v", err)
|
||
http.Error(w, "Veritabanı hatası", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
groups := make([]models.ProductionHasCostDetailGroup, 0, 16)
|
||
groupIndexByName := map[string]int{}
|
||
scannedRows := 0
|
||
scanErrors := 0
|
||
|
||
for rows.Next() {
|
||
var (
|
||
groupName string
|
||
groupTotal float64
|
||
groupTotalUSD float64
|
||
fiyatGirilen sql.NullFloat64
|
||
fiyatDoviz sql.NullString
|
||
maliyeteDahil sql.NullBool
|
||
cmPriceTypeID sql.NullInt64
|
||
item models.ProductionHasCostDetailGroupItem
|
||
)
|
||
|
||
if err := rows.Scan(
|
||
&groupName,
|
||
&groupTotal,
|
||
&groupTotalUSD,
|
||
&item.NOnMLNo,
|
||
&item.NOnMLDetNo,
|
||
&item.NHammaddeTuruNo,
|
||
&item.SKodu,
|
||
&item.SAciklama,
|
||
&item.SRenk,
|
||
&item.SBeden,
|
||
&item.SAciklama2,
|
||
&item.LMiktar,
|
||
&item.LFiyat,
|
||
&item.LTutar,
|
||
&item.SFiyatTipi,
|
||
&item.SDovizCinsi,
|
||
&item.LDovizKuru,
|
||
&item.LDovizFiyati,
|
||
&fiyatGirilen,
|
||
&fiyatDoviz,
|
||
&maliyeteDahil,
|
||
&cmPriceTypeID,
|
||
&item.USDTutar,
|
||
&item.EURTutar,
|
||
&item.GBPTutar,
|
||
&item.SBirim,
|
||
&item.SHammaddeTuruAdi,
|
||
&item.SParcaAdi,
|
||
); err != nil {
|
||
log.Printf("âš ï¸ [ProductionHasCostDetailGroups] scan error: %v", err)
|
||
continue
|
||
}
|
||
scannedRows += 1
|
||
|
||
if fiyatGirilen.Valid {
|
||
item.FiyatGirilen = new(float64)
|
||
*item.FiyatGirilen = fiyatGirilen.Float64
|
||
}
|
||
if fiyatDoviz.Valid {
|
||
item.FiyatDoviz = strings.TrimSpace(fiyatDoviz.String)
|
||
}
|
||
item.MaliyeteDahil = maliyeteDahil.Valid && maliyeteDahil.Bool
|
||
if cmPriceTypeID.Valid {
|
||
value := int(cmPriceTypeID.Int64)
|
||
item.CMPriceTypeID = &value
|
||
}
|
||
|
||
idx, ok := groupIndexByName[groupName]
|
||
if !ok {
|
||
groups = append(groups, models.ProductionHasCostDetailGroup{
|
||
SAciklama3: groupName,
|
||
TotalTutar: groupTotal,
|
||
TotalUSDTutar: groupTotalUSD,
|
||
Items: make([]models.ProductionHasCostDetailGroupItem, 0, 24),
|
||
})
|
||
idx = len(groups) - 1
|
||
groupIndexByName[groupName] = idx
|
||
}
|
||
|
||
groups[idx].Items = append(groups[idx].Items, item)
|
||
}
|
||
|
||
if err := rows.Err(); err != nil {
|
||
log.Printf("âš ï¸ [ProductionHasCostDetailGroups] rows error: %v", err)
|
||
http.Error(w, "Veritabanı satır hatası", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
logger.Info("request done", "group_count", len(groups), "row_count", scannedRows, "scan_errors", scanErrors)
|
||
log.Printf("[ProductionHasCostDetailGroups] done n_onml_no=%d groups=%d rows=%d scan_errors=%d", nOnMLNo, len(groups), scannedRows, scanErrors)
|
||
_ = json.NewEncoder(w).Encode(groups)
|
||
}
|
||
|
||
// GET /api/pricing/production-product-costing/has-cost-detail-header
|
||
func GetProductionHasCostDetailHeaderHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
uretimDB := db.GetUretimDB()
|
||
if uretimDB == nil {
|
||
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
mssqlDB := db.GetDB()
|
||
|
||
detailSource := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("detail_source")))
|
||
recipeCode := strings.TrimSpace(r.URL.Query().Get("recete_kodu"))
|
||
productCode := strings.TrimSpace(r.URL.Query().Get("urun_kodu"))
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
logger := utils.SlogFromContext(ctx).With(
|
||
"handler", "production-product-costing.detail-header",
|
||
"detail_source", detailSource,
|
||
"urun_kodu", productCode,
|
||
"recete_kodu", recipeCode,
|
||
)
|
||
|
||
if detailSource == "no-cost" || recipeCode != "" {
|
||
if recipeCode == "" {
|
||
logger.Warn("request invalid", "reason", "missing recete_kodu")
|
||
http.Error(w, "recete_kodu zorunlu", http.StatusBadRequest)
|
||
return
|
||
}
|
||
logger.Info("request start")
|
||
|
||
row, err := queries.GetProductionNoCostDetailHeaderByRecipeCode(ctx, uretimDB, recipeCode, productCode)
|
||
if err != nil {
|
||
logger.Error("query prepare error", "err", err)
|
||
log.Printf("❌ [ProductionNoCostDetailHeader] query prepare error: %v", err)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
var item models.ProductionHasCostDetailHeader
|
||
if err := row.Scan(
|
||
&item.UretimiYapanFirma,
|
||
&item.SonIsEmriVeren,
|
||
&item.NOnMLNo,
|
||
&item.UrunKodu,
|
||
&item.UrunAdi,
|
||
&item.UretimSekliID,
|
||
&item.UretimSekli,
|
||
&item.DteKayitTarihi,
|
||
&item.SKullaniciAdi,
|
||
&item.LTutarTL,
|
||
&item.LTutarUSD,
|
||
&item.LTutarEURO,
|
||
&item.LTutarGBP,
|
||
&item.SDovizCinsi,
|
||
&item.LTutarDoviz,
|
||
&item.DteGuncellemeTarihi,
|
||
&item.SGuncellemeKullaniciAdi,
|
||
&item.NUrtReceteID,
|
||
); err != nil {
|
||
logger.Warn("scan or not found", "err", err)
|
||
if err == sql.ErrNoRows {
|
||
logger.Warn("row not found")
|
||
http.Error(w, "Kayit bulunamadi", http.StatusNotFound)
|
||
return
|
||
}
|
||
log.Printf("❌ [ProductionNoCostDetailHeader] scan error: %v", err)
|
||
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
logger.Info("request done", "n_urt_recete_id", item.NUrtReceteID, "urun_kodu", item.UrunKodu)
|
||
if mssqlDB != nil {
|
||
ilk, ana, alt, err := queries.GetProductIlkAnaAltGrupByUrunKodu(ctx, mssqlDB, item.UrunKodu)
|
||
if err != nil {
|
||
logger.Warn("product group query error", "err", err)
|
||
} else {
|
||
item.UrunIlkGrubu = ilk
|
||
item.UrunAnaGrubu = ana
|
||
item.UrunAltGrubu = alt
|
||
}
|
||
}
|
||
_ = json.NewEncoder(w).Encode(item)
|
||
return
|
||
}
|
||
|
||
rawOnMLNo := strings.TrimSpace(r.URL.Query().Get("n_onml_no"))
|
||
nOnMLNo, err := strconv.Atoi(rawOnMLNo)
|
||
if err != nil || nOnMLNo <= 0 {
|
||
logger.Warn("request invalid", "reason", "invalid n_onml_no", "raw_n_onml_no", rawOnMLNo)
|
||
http.Error(w, "n_onml_no zorunlu ve pozitif sayi olmali", http.StatusBadRequest)
|
||
return
|
||
}
|
||
logger = logger.With("n_onml_no", nOnMLNo)
|
||
logger.Info("request start")
|
||
|
||
row, err := queries.GetProductionHasCostDetailHeaderByOnMLNo(ctx, uretimDB, nOnMLNo)
|
||
if err != nil {
|
||
logger.Error("query prepare error", "err", err)
|
||
log.Printf("❌ [ProductionHasCostDetailHeader] query prepare error: %v", err)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
var item models.ProductionHasCostDetailHeader
|
||
if err := row.Scan(
|
||
&item.UretimiYapanFirma,
|
||
&item.SonIsEmriVeren,
|
||
&item.NOnMLNo,
|
||
&item.UrunKodu,
|
||
&item.UrunAdi,
|
||
&item.UretimSekliID,
|
||
&item.UretimSekli,
|
||
&item.DteKayitTarihi,
|
||
&item.SKullaniciAdi,
|
||
&item.LTutarTL,
|
||
&item.LTutarUSD,
|
||
&item.LTutarEURO,
|
||
&item.LTutarGBP,
|
||
&item.SDovizCinsi,
|
||
&item.LTutarDoviz,
|
||
&item.DteGuncellemeTarihi,
|
||
&item.SGuncellemeKullaniciAdi,
|
||
&item.NUrtReceteID,
|
||
); err != nil {
|
||
logger.Warn("scan or not found", "err", err)
|
||
if err == sql.ErrNoRows {
|
||
http.Error(w, "Kayit bulunamadi", http.StatusNotFound)
|
||
return
|
||
}
|
||
log.Printf("❌ [ProductionHasCostDetailHeader] scan error: %v", err)
|
||
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
logger.Info("request done", "n_onml_no", item.NOnMLNo, "urun_kodu", item.UrunKodu, "n_urt_recete_id", item.NUrtReceteID)
|
||
if mssqlDB != nil {
|
||
ilk, ana, alt, err := queries.GetProductIlkAnaAltGrupByUrunKodu(ctx, mssqlDB, item.UrunKodu)
|
||
if err != nil {
|
||
logger.Warn("product group query error", "err", err)
|
||
} else {
|
||
item.UrunIlkGrubu = ilk
|
||
item.UrunAnaGrubu = ana
|
||
item.UrunAltGrubu = alt
|
||
}
|
||
}
|
||
_ = json.NewEncoder(w).Encode(item)
|
||
}
|
||
|
||
// GET /api/pricing/production-product-costing/production-types
|
||
func GetProductionTypesHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
uretimDB := db.GetUretimDB()
|
||
if uretimDB == nil {
|
||
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.production-types")
|
||
logger.Info("request start")
|
||
|
||
rows, err := queries.GetProductionTypes(ctx, uretimDB)
|
||
if err != nil {
|
||
logger.Error("query error", "err", err)
|
||
log.Printf("❌ [ProductionTypes] query error: %v", err)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
var types []models.ProductionType
|
||
for rows.Next() {
|
||
var t models.ProductionType
|
||
if err := rows.Scan(&t.ID, &t.Aciklama); err != nil {
|
||
logger.Warn("scan error", "err", err)
|
||
log.Printf("⚠️ [ProductionTypes] scan error: %v", err)
|
||
continue
|
||
}
|
||
types = append(types, t)
|
||
}
|
||
|
||
if err := rows.Err(); err != nil {
|
||
logger.Error("rows error", "err", err)
|
||
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
logger.Info("request done", "row_count", len(types))
|
||
_ = json.NewEncoder(w).Encode(types)
|
||
}
|
||
|
||
// GET /api/pricing/production-product-costing/detail-editor-options
|
||
func GetProductionHasCostDetailEditorOptionsHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
kind := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("kind")))
|
||
search := strings.TrimSpace(r.URL.Query().Get("search"))
|
||
limit := parsePositiveIntOrDefault(r.URL.Query().Get("limit"), 100)
|
||
if limit > 200 {
|
||
limit = 200
|
||
}
|
||
logger := utils.SlogFromContext(ctx).With(
|
||
"handler", "production-product-costing.detail-editor-options",
|
||
"kind", kind,
|
||
"search", search,
|
||
"limit", limit,
|
||
)
|
||
logger.Info("request start")
|
||
|
||
switch kind {
|
||
case "hammadde":
|
||
uretimDB := db.GetUretimDB()
|
||
if uretimDB == nil {
|
||
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
rows, err := queries.GetProductionHasCostDetailHammaddeTypeOptions(ctx, uretimDB, search, limit)
|
||
if err != nil {
|
||
logger.Error("hammadde query error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] hammadde query error: %v", err)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
if columns, columnErr := rows.Columns(); columnErr != nil {
|
||
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] item columns read error search=%q limit=%d err=%v", search, limit, columnErr)
|
||
} else {
|
||
log.Printf("[ProductionHasCostDetailEditorOptions] item columns=%v", columns)
|
||
}
|
||
|
||
list := make([]models.ProductionHasCostDetailEditorOption, 0, limit)
|
||
for rows.Next() {
|
||
var item models.ProductionHasCostDetailEditorOption
|
||
if err := rows.Scan(&item.NHammaddeTuruNo, &item.SHammaddeTuruAdi, &item.SAciklama3, &item.MTUrtMTBolumID, &item.SParcaAdi); err != nil {
|
||
logger.Warn("hammadde scan error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] hammadde scan error: %v", err)
|
||
continue
|
||
}
|
||
item.Kind = "hammadde"
|
||
item.Value = item.NHammaddeTuruNo
|
||
item.Label = strings.TrimSpace(item.NHammaddeTuruNo + " - " + item.SHammaddeTuruAdi)
|
||
list = append(list, item)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Error("hammadde rows error", "err", err)
|
||
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
logger.Info("request done", "kind", "hammadde", "row_count", len(list))
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
return
|
||
|
||
case "item":
|
||
uretimDB := db.GetUretimDB()
|
||
if uretimDB == nil {
|
||
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
log.Printf("[ProductionHasCostDetailEditorOptions] item start search=%q limit=%d", search, limit)
|
||
rows, err := queries.GetProductionHasCostDetailItemOptions(ctx, uretimDB, search, limit)
|
||
if err != nil {
|
||
logger.Error("item query error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] item query error search=%q limit=%d err=%v", search, limit, err)
|
||
logProductionHasCostDetailEditorOptionItemDiagnostics(ctx, uretimDB, search, limit)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
if columns, columnErr := rows.Columns(); columnErr != nil {
|
||
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] item columns read error search=%q limit=%d err=%v", search, limit, columnErr)
|
||
} else {
|
||
log.Printf("[ProductionHasCostDetailEditorOptions] item columns=%v", columns)
|
||
}
|
||
|
||
list := make([]models.ProductionHasCostDetailEditorOption, 0, limit)
|
||
scanErrors := 0
|
||
for rows.Next() {
|
||
var item models.ProductionHasCostDetailEditorOption
|
||
if err := rows.Scan(&item.NStokID, &item.SKodu, &item.SAciklama, &item.SModel, &item.SBirim); err != nil {
|
||
scanErrors += 1
|
||
logger.Warn("item scan error", "scan_index", len(list)+scanErrors, "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] item scan error scan_index=%d err=%v", len(list)+scanErrors, err)
|
||
continue
|
||
}
|
||
item.Kind = "item"
|
||
item.Value = item.SKodu
|
||
item.Label = strings.TrimSpace(item.SKodu + " - " + item.SAciklama)
|
||
list = append(list, item)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Error("item rows error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] item rows error search=%q limit=%d err=%v", search, limit, err)
|
||
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
logger.Info("request done", "kind", "item", "row_count", len(list), "scan_errors", scanErrors)
|
||
log.Printf("[ProductionHasCostDetailEditorOptions] item done search=%q limit=%d row_count=%d scan_errors=%d", search, limit, len(list), scanErrors)
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
return
|
||
|
||
case "color":
|
||
uretimDB := db.GetUretimDB()
|
||
if uretimDB == nil {
|
||
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
modelCode := strings.TrimSpace(r.URL.Query().Get("model_code"))
|
||
if modelCode == "" {
|
||
_ = json.NewEncoder(w).Encode([]models.ProductionHasCostDetailEditorOption{})
|
||
return
|
||
}
|
||
|
||
rows, err := queries.GetProductionHasCostDetailColorOptions(ctx, uretimDB, modelCode, search, limit)
|
||
if err != nil {
|
||
logger.Error("color query error", "model_code", modelCode, "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] color query error: %v", err)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
list := make([]models.ProductionHasCostDetailEditorOption, 0, limit)
|
||
for rows.Next() {
|
||
var item models.ProductionHasCostDetailEditorOption
|
||
if err := rows.Scan(&item.ColorCode, &item.ColorDescription); err != nil {
|
||
logger.Warn("color scan error", "model_code", modelCode, "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailEditorOptions] color scan error: %v", err)
|
||
continue
|
||
}
|
||
item.Kind = "color"
|
||
item.Value = item.ColorCode
|
||
item.Label = strings.TrimSpace(item.ColorCode + " - " + item.ColorDescription)
|
||
list = append(list, item)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Error("color rows error", "model_code", modelCode, "err", err)
|
||
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
logger.Info("request done", "kind", "color", "model_code", modelCode, "row_count", len(list))
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
return
|
||
}
|
||
|
||
logger.Warn("request invalid", "reason", "invalid kind")
|
||
http.Error(w, "kind hammadde, item veya color olmali", http.StatusBadRequest)
|
||
}
|
||
|
||
// GET /api/pricing/production-product-costing/has-cost-detail-exchange-rates
|
||
func GetProductionHasCostDetailExchangeRatesHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
mssqlDB := db.GetDB()
|
||
if mssqlDB == nil {
|
||
http.Error(w, "MSSQL veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.exchange-rates")
|
||
|
||
rawCostDate := strings.TrimSpace(r.URL.Query().Get("maliyet_tarihi"))
|
||
costDate := ""
|
||
if rawCostDate != "" {
|
||
parsedDate, err := time.Parse("2006-01-02", rawCostDate)
|
||
if err != nil {
|
||
logger.Warn("request invalid", "reason", "invalid maliyet_tarihi", "maliyet_tarihi", rawCostDate)
|
||
http.Error(w, "maliyet_tarihi YYYY-MM-DD formatinda olmali", http.StatusBadRequest)
|
||
return
|
||
}
|
||
costDate = parsedDate.Format("2006-01-02")
|
||
}
|
||
|
||
logger.Info("request start", "maliyet_tarihi", costDate)
|
||
log.Printf("[ProductionHasCostDetailExchangeRates] start maliyet_tarihi=%s", costDate)
|
||
|
||
row, err := queries.GetProductionHasCostDetailExchangeRatesByDate(ctx, mssqlDB, costDate)
|
||
if err != nil {
|
||
logger.Error("query prepare error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailExchangeRates] query prepare error: %v", err)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
item := models.ProductionHasCostDetailExchangeRates{
|
||
TRYRate: 1,
|
||
}
|
||
if err := row.Scan(
|
||
&item.RateDate,
|
||
&item.USDRate,
|
||
&item.EURRate,
|
||
&item.GBPRate,
|
||
); err != nil {
|
||
if err == sql.ErrNoRows {
|
||
item.RateDate = costDate
|
||
logger.Info("request done", "rate_date", item.RateDate, "fallback", true)
|
||
_ = json.NewEncoder(w).Encode(item)
|
||
return
|
||
}
|
||
log.Printf("⚠️ [ProductionHasCostDetailExchangeRates] scan error: %v", err)
|
||
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
logger.Info("request done", "rate_date", item.RateDate, "usd_rate", item.USDRate, "eur_rate", item.EURRate, "gbp_rate", item.GBPRate)
|
||
log.Printf("[ProductionHasCostDetailExchangeRates] done maliyet_tarihi=%s rate_date=%s usd=%.4f eur=%.4f", costDate, item.RateDate, item.USDRate, item.EURRate)
|
||
_ = json.NewEncoder(w).Encode(item)
|
||
}
|
||
|
||
// POST /api/pricing/production-product-costing/has-cost-detail-bulk-prices
|
||
func PostProductionHasCostDetailBulkPricesHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
mssqlDB := db.GetDB()
|
||
if mssqlDB == nil {
|
||
http.Error(w, "MSSQL veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.bulk-prices")
|
||
|
||
var req models.ProductionHasCostDetailBulkPriceRequest
|
||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
logger.Warn("request invalid", "reason", "invalid request body", "err", err)
|
||
http.Error(w, "Gecersiz istek govdesi", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
costDate := strings.TrimSpace(req.MaliyetTarihi)
|
||
itemsCount := len(req.Items)
|
||
responseChan := make(chan *models.ProductionHasCostDetailBulkPriceRow, itemsCount)
|
||
|
||
logger.Info("request start",
|
||
"n_onml_no", strings.TrimSpace(req.NOnMLNo),
|
||
"urun_kodu", strings.TrimSpace(req.UrunKodu),
|
||
"maliyet_tarihi", costDate,
|
||
"item_count", itemsCount,
|
||
)
|
||
log.Printf("[ProductionHasCostDetailBulkPrices] start n_onml_no=%s urun_kodu=%s maliyet_tarihi=%s item_count=%d", strings.TrimSpace(req.NOnMLNo), strings.TrimSpace(req.UrunKodu), costDate, itemsCount)
|
||
|
||
for _, item := range req.Items {
|
||
go func(item models.ProductionHasCostDetailPriceLookupItem) {
|
||
sKodu := normalizeLookupValue(item.SKodu)
|
||
if sKodu == "" {
|
||
responseChan <- nil
|
||
return
|
||
}
|
||
|
||
colorCode := firstNonEmptyString(
|
||
normalizeLookupValue(item.ColorCode),
|
||
normalizeLookupValue(item.SRenk),
|
||
)
|
||
itemDim1Code := firstNonEmptyString(
|
||
normalizeLookupValue(item.ItemDim1Code),
|
||
)
|
||
|
||
row, err := queries.GetProductionHasCostLatestPurchasePriceForItem(
|
||
ctx,
|
||
mssqlDB,
|
||
sKodu,
|
||
colorCode,
|
||
itemDim1Code,
|
||
costDate,
|
||
)
|
||
if err != nil {
|
||
logger.Warn("item lookup error", "s_kodu", sKodu, "color_code", colorCode, "item_dim1_code", itemDim1Code, "err", err)
|
||
responseChan <- nil
|
||
return
|
||
}
|
||
|
||
var result models.ProductionHasCostDetailBulkPriceRow
|
||
if err := row.Scan(
|
||
&result.PriceType,
|
||
&result.Tarih,
|
||
&result.FaturaKodu,
|
||
&result.MasrafKodu,
|
||
&result.MasrafDetay,
|
||
&result.ColorCode,
|
||
&result.ColorDescription,
|
||
&result.ItemDim1Code,
|
||
&result.ItemDim1Description,
|
||
&result.FiyatGirilen,
|
||
&result.FiyatDoviz,
|
||
); err != nil {
|
||
logger.Warn("item scan error", "s_kodu", sKodu, "color_code", colorCode, "item_dim1_code", itemDim1Code, "err", err)
|
||
responseChan <- nil
|
||
return
|
||
}
|
||
|
||
result.RowKey = strings.TrimSpace(item.RowKey)
|
||
result.NOnMLDetNo = strings.TrimSpace(item.NOnMLDetNo)
|
||
result.NHammaddeTuruNo = strings.TrimSpace(item.NHammaddeTuruNo)
|
||
result.SKodu = sKodu
|
||
|
||
if strings.TrimSpace(result.ColorCode) == "" {
|
||
result.ColorCode = colorCode
|
||
}
|
||
if strings.TrimSpace(result.ItemDim1Code) == "" {
|
||
result.ItemDim1Code = itemDim1Code
|
||
}
|
||
|
||
responseChan <- &result
|
||
}(item)
|
||
}
|
||
|
||
response := make([]models.ProductionHasCostDetailBulkPriceRow, 0, itemsCount)
|
||
for i := 0; i < itemsCount; i++ {
|
||
res := <-responseChan
|
||
if res != nil {
|
||
response = append(response, *res)
|
||
}
|
||
}
|
||
|
||
logger.Info("request done", "item_count", itemsCount, "matched_count", len(response))
|
||
log.Printf("[ProductionHasCostDetailBulkPrices] done item_count=%d matched=%d", itemsCount, len(response))
|
||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||
"items": response,
|
||
})
|
||
}
|
||
|
||
// GET /api/pricing/production-product-costing/has-cost-detail-line-history
|
||
func GetProductionHasCostDetailLineHistoryHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
uretimDB := db.GetUretimDB()
|
||
mssqlDB := db.GetDB()
|
||
if uretimDB == nil && mssqlDB == nil {
|
||
http.Error(w, "Veritabani baglantilari aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.line-history")
|
||
|
||
rawOnMLNo := strings.TrimSpace(r.URL.Query().Get("n_onml_no"))
|
||
currentOnMLNo, _ := strconv.Atoi(rawOnMLNo)
|
||
nHammaddeTuruNo := strings.TrimSpace(r.URL.Query().Get("n_hammadde_turu_no"))
|
||
sKodu := normalizeLookupValue(r.URL.Query().Get("s_kodu"))
|
||
colorCode := firstNonEmptyString(
|
||
normalizeLookupValue(r.URL.Query().Get("color_code")),
|
||
normalizeLookupValue(r.URL.Query().Get("s_renk")),
|
||
)
|
||
costDate := strings.TrimSpace(r.URL.Query().Get("maliyet_tarihi"))
|
||
|
||
if sKodu == "" {
|
||
logger.Warn("request invalid", "reason", "missing s_kodu")
|
||
http.Error(w, "s_kodu zorunlu", http.StatusBadRequest)
|
||
return
|
||
}
|
||
logger.Info("request start", "n_onml_no", currentOnMLNo, "n_hammadde_turu_no", nHammaddeTuruNo, "s_kodu", sKodu, "color_code", colorCode, "maliyet_tarihi", costDate)
|
||
log.Printf("[ProductionHasCostDetailLineHistory] start n_onml_no=%d n_hammadde_turu_no=%s s_kodu=%s color=%s maliyet_tarihi=%s", currentOnMLNo, nHammaddeTuruNo, sKodu, colorCode, costDate)
|
||
|
||
const LINE_HISTORY_ROW_LIMIT = 500
|
||
|
||
response := models.ProductionHasCostDetailLineHistoryResponse{
|
||
PurchaseRows: make([]models.ProductionHasCostDetailPurchaseHistoryRow, 0, LINE_HISTORY_ROW_LIMIT),
|
||
RecipeRows: make([]models.ProductionHasCostDetailRecipeHistoryRow, 0, LINE_HISTORY_ROW_LIMIT),
|
||
}
|
||
allowLegacyAutoFallback := true
|
||
|
||
if mssqlDB != nil {
|
||
rows, err := queries.GetProductionHasCostPurchaseHistoryByExpenseCode(
|
||
ctx,
|
||
mssqlDB,
|
||
sKodu,
|
||
costDate,
|
||
LINE_HISTORY_ROW_LIMIT,
|
||
)
|
||
if err != nil {
|
||
logger.Warn("purchase query error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] purchase query error: %v", err)
|
||
} else {
|
||
defer rows.Close()
|
||
for rows.Next() {
|
||
var item models.ProductionHasCostDetailPurchaseHistoryRow
|
||
if err := rows.Scan(
|
||
&item.SourceType,
|
||
&item.PriceType,
|
||
&item.Tarih,
|
||
&item.FaturaKodu,
|
||
&item.FirmaKodu,
|
||
&item.FirmaAciklama,
|
||
&item.MasrafKodu,
|
||
&item.MasrafDetay,
|
||
&item.ColorCode,
|
||
&item.ColorDescription,
|
||
&item.ItemDim1Code,
|
||
&item.ItemDim1Description,
|
||
&item.Miktar,
|
||
&item.BIRIM,
|
||
&item.EvrakFiyat,
|
||
&item.EvrakTutar,
|
||
&item.EvrakDoviz,
|
||
); err != nil {
|
||
logger.Warn("purchase scan error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] purchase scan error: %v", err)
|
||
continue
|
||
}
|
||
response.PurchaseRows = append(response.PurchaseRows, item)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Warn("purchase rows error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] purchase rows error: %v", err)
|
||
}
|
||
}
|
||
}
|
||
|
||
if allowLegacyAutoFallback && mssqlDB != nil && len(response.PurchaseRows) == 0 {
|
||
similarPrefix := queries.BuildProductionHasCostSimilarCodePrefix(sKodu)
|
||
if similarPrefix != "" {
|
||
logger.Info("purchase fallback start", "s_kodu", sKodu, "similar_prefix", similarPrefix, "maliyet_tarihi", costDate)
|
||
rows, err := queries.GetProductionHasCostPurchaseHistoryByCodePrefix(
|
||
ctx,
|
||
mssqlDB,
|
||
similarPrefix,
|
||
costDate,
|
||
LINE_HISTORY_ROW_LIMIT,
|
||
)
|
||
if err != nil {
|
||
logger.Warn("purchase fallback query error", "err", err)
|
||
log.Printf("âš ï¸ [ProductionHasCostDetailLineHistory] purchase fallback query error: %v", err)
|
||
} else {
|
||
defer rows.Close()
|
||
for rows.Next() {
|
||
var item models.ProductionHasCostDetailPurchaseHistoryRow
|
||
if err := rows.Scan(
|
||
&item.SourceType,
|
||
&item.PriceType,
|
||
&item.Tarih,
|
||
&item.FaturaKodu,
|
||
&item.FirmaKodu,
|
||
&item.FirmaAciklama,
|
||
&item.MasrafKodu,
|
||
&item.MasrafDetay,
|
||
&item.ColorCode,
|
||
&item.ColorDescription,
|
||
&item.ItemDim1Code,
|
||
&item.ItemDim1Description,
|
||
&item.Miktar,
|
||
&item.BIRIM,
|
||
&item.EvrakFiyat,
|
||
&item.EvrakTutar,
|
||
&item.EvrakDoviz,
|
||
); err != nil {
|
||
logger.Warn("purchase fallback scan error", "err", err)
|
||
log.Printf("âš ï¸ [ProductionHasCostDetailLineHistory] purchase fallback scan error: %v", err)
|
||
continue
|
||
}
|
||
response.PurchaseRows = append(response.PurchaseRows, item)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Warn("purchase fallback rows error", "err", err)
|
||
log.Printf("âš ï¸ [ProductionHasCostDetailLineHistory] purchase fallback rows error: %v", err)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if uretimDB != nil {
|
||
rows, err := queries.GetProductionHasCostRecipeHistoryByExpenseCode(
|
||
ctx,
|
||
uretimDB,
|
||
currentOnMLNo,
|
||
sKodu,
|
||
colorCode,
|
||
costDate,
|
||
LINE_HISTORY_ROW_LIMIT,
|
||
)
|
||
if err != nil {
|
||
logger.Warn("recipe query error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe query error: %v", err)
|
||
} else {
|
||
defer rows.Close()
|
||
for rows.Next() {
|
||
var item models.ProductionHasCostDetailRecipeHistoryRow
|
||
if err := rows.Scan(
|
||
&item.SourceType,
|
||
&item.PriceType,
|
||
&item.DteIslemTarihi,
|
||
&item.NOnMLNo,
|
||
&item.FirmaKodu,
|
||
&item.FirmaAciklama,
|
||
&item.SKodu,
|
||
&item.SAciklama,
|
||
&item.SRenk,
|
||
&item.LMiktar,
|
||
&item.SBirim,
|
||
&item.LDovizFiyati,
|
||
&item.LDovizTutari,
|
||
&item.USD,
|
||
&item.DUMMY,
|
||
); err != nil {
|
||
logger.Warn("recipe scan error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe scan error: %v", err)
|
||
continue
|
||
}
|
||
response.RecipeRows = append(response.RecipeRows, item)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Warn("recipe rows error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe rows error: %v", err)
|
||
}
|
||
}
|
||
}
|
||
|
||
if allowLegacyAutoFallback && uretimDB != nil && len(response.RecipeRows) == 0 {
|
||
similarPrefix := queries.BuildProductionHasCostSimilarCodePrefix(sKodu)
|
||
if similarPrefix != "" {
|
||
logger.Info("recipe fallback prefix start", "s_kodu", sKodu, "similar_prefix", similarPrefix, "maliyet_tarihi", costDate)
|
||
rows, err := queries.GetProductionHasCostOnMLHistoryByCodePrefix(
|
||
ctx,
|
||
uretimDB,
|
||
similarPrefix,
|
||
costDate,
|
||
LINE_HISTORY_ROW_LIMIT,
|
||
)
|
||
if err != nil {
|
||
logger.Warn("recipe fallback prefix query error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe fallback prefix query error: %v", err)
|
||
} else {
|
||
defer rows.Close()
|
||
for rows.Next() {
|
||
var item models.ProductionHasCostDetailRecipeHistoryRow
|
||
if err := rows.Scan(
|
||
&item.SourceType,
|
||
&item.PriceType,
|
||
&item.DteIslemTarihi,
|
||
&item.NOnMLNo,
|
||
&item.FirmaKodu,
|
||
&item.FirmaAciklama,
|
||
&item.SKodu,
|
||
&item.SAciklama,
|
||
&item.SRenk,
|
||
&item.LMiktar,
|
||
&item.SBirim,
|
||
&item.LDovizFiyati,
|
||
&item.LDovizTutari,
|
||
&item.USD,
|
||
&item.DUMMY,
|
||
); err != nil {
|
||
logger.Warn("recipe fallback prefix scan error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe fallback prefix scan error: %v", err)
|
||
continue
|
||
}
|
||
response.RecipeRows = append(response.RecipeRows, item)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Warn("recipe fallback prefix rows error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe fallback prefix rows error: %v", err)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if allowLegacyAutoFallback && uretimDB != nil && len(response.RecipeRows) == 0 && nHammaddeTuruNo != "" {
|
||
logger.Info("recipe fallback hammadde-turu start", "n_hammadde_turu_no", nHammaddeTuruNo, "maliyet_tarihi", costDate)
|
||
rows, err := queries.GetProductionHasCostOnMLHistoryByHammaddeTuruNo(
|
||
ctx,
|
||
uretimDB,
|
||
nHammaddeTuruNo,
|
||
costDate,
|
||
LINE_HISTORY_ROW_LIMIT,
|
||
)
|
||
if err != nil {
|
||
logger.Warn("recipe fallback hammadde-turu query error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe fallback hammadde-turu query error: %v", err)
|
||
} else {
|
||
defer rows.Close()
|
||
for rows.Next() {
|
||
var item models.ProductionHasCostDetailRecipeHistoryRow
|
||
if err := rows.Scan(
|
||
&item.SourceType,
|
||
&item.PriceType,
|
||
&item.DteIslemTarihi,
|
||
&item.NOnMLNo,
|
||
&item.FirmaKodu,
|
||
&item.FirmaAciklama,
|
||
&item.SKodu,
|
||
&item.SAciklama,
|
||
&item.SRenk,
|
||
&item.LMiktar,
|
||
&item.SBirim,
|
||
&item.LDovizFiyati,
|
||
&item.LDovizTutari,
|
||
&item.USD,
|
||
&item.DUMMY,
|
||
); err != nil {
|
||
logger.Warn("recipe fallback hammadde-turu scan error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe fallback hammadde-turu scan error: %v", err)
|
||
continue
|
||
}
|
||
response.RecipeRows = append(response.RecipeRows, item)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Warn("recipe fallback hammadde-turu rows error", "err", err)
|
||
log.Printf("⚠️ [ProductionHasCostDetailLineHistory] recipe fallback hammadde-turu rows error: %v", err)
|
||
}
|
||
}
|
||
}
|
||
|
||
logger.Info("request done", "s_kodu", sKodu, "purchase_count", len(response.PurchaseRows), "recipe_count", len(response.RecipeRows))
|
||
log.Printf("[ProductionHasCostDetailLineHistory] done s_kodu=%s purchase_rows=%d recipe_rows=%d", sKodu, len(response.PurchaseRows), len(response.RecipeRows))
|
||
_ = json.NewEncoder(w).Encode(response)
|
||
}
|
||
|
||
// GET /api/pricing/production-product-costing/has-cost-detail-similar-history
|
||
func GetProductionHasCostDetailSimilarHistoryHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
mssqlDB := db.GetDB()
|
||
uretimDB := db.GetUretimDB()
|
||
if mssqlDB == nil || uretimDB == nil {
|
||
http.Error(w, "Veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.similar-history")
|
||
|
||
q := r.URL.Query()
|
||
nHammaddeTuruNo := strings.TrimSpace(q.Get("n_hammadde_turu_no"))
|
||
sKodu := normalizeLookupValue(q.Get("s_kodu"))
|
||
costDate := strings.TrimSpace(q.Get("maliyet_tarihi"))
|
||
limit := parsePositiveIntOrDefault(q.Get("limit"), 500)
|
||
searchMode := strings.ToLower(strings.TrimSpace(q.Get("search_mode")))
|
||
if searchMode == "" || searchMode == "exact" {
|
||
searchMode = "prefix"
|
||
}
|
||
similarPrefix := queries.BuildProductionHasCostSimilarCodePrefix(sKodu)
|
||
|
||
if nHammaddeTuruNo == "" && sKodu == "" {
|
||
http.Error(w, "n_hammadde_turu_no veya s_kodu parametresi zorunludur", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
logger.Info("request start", "search_mode", searchMode, "n_hammadde_turu_no", nHammaddeTuruNo, "s_kodu", sKodu, "similar_prefix", similarPrefix, "maliyet_tarihi", costDate, "limit", limit)
|
||
|
||
response := models.ProductionHasCostDetailLineHistoryResponse{
|
||
PurchaseRows: make([]models.ProductionHasCostDetailPurchaseHistoryRow, 0, limit),
|
||
RecipeRows: make([]models.ProductionHasCostDetailRecipeHistoryRow, 0, limit),
|
||
}
|
||
purchaseMatchStage := searchMode
|
||
recipeMatchStage := searchMode
|
||
allowRecipeAutoFallback := false
|
||
|
||
if searchMode == "alternative" {
|
||
purchaseMatchStage = "skipped"
|
||
if nHammaddeTuruNo != "" && uretimDB != nil {
|
||
rows, err := queries.GetProductionHasCostOnMLHistoryByHammaddeTuruNo(ctx, uretimDB, nHammaddeTuruNo, costDate, limit)
|
||
if err != nil {
|
||
logger.Warn("alternative onml query error", "err", err)
|
||
http.Error(w, "Sorgu calistirilirken hata olustu", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
for rows.Next() {
|
||
var item models.ProductionHasCostDetailRecipeHistoryRow
|
||
if err := rows.Scan(
|
||
&item.SourceType,
|
||
&item.PriceType,
|
||
&item.DteIslemTarihi,
|
||
&item.NOnMLNo,
|
||
&item.FirmaKodu,
|
||
&item.FirmaAciklama,
|
||
&item.SKodu,
|
||
&item.SAciklama,
|
||
&item.SRenk,
|
||
&item.LMiktar,
|
||
&item.SBirim,
|
||
&item.LDovizFiyati,
|
||
&item.LDovizTutari,
|
||
&item.USD,
|
||
&item.DUMMY,
|
||
); err != nil {
|
||
logger.Warn("alternative onml scan error", "err", err)
|
||
continue
|
||
}
|
||
response.RecipeRows = append(response.RecipeRows, item)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Warn("alternative onml rows error", "err", err)
|
||
}
|
||
}
|
||
|
||
if len(response.PurchaseRows) == 0 {
|
||
purchaseMatchStage = "empty"
|
||
}
|
||
if len(response.RecipeRows) == 0 {
|
||
recipeMatchStage = "empty"
|
||
}
|
||
|
||
logger.Info("request done",
|
||
"search_mode", searchMode,
|
||
"purchase_match_stage", purchaseMatchStage,
|
||
"recipe_match_stage", recipeMatchStage,
|
||
"similar_prefix", similarPrefix,
|
||
"purchase_count", len(response.PurchaseRows),
|
||
"recipe_count", len(response.RecipeRows),
|
||
)
|
||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||
"purchaseRows": response.PurchaseRows,
|
||
"recipeRows": response.RecipeRows,
|
||
"purchase_match_stage": purchaseMatchStage,
|
||
"recipe_match_stage": recipeMatchStage,
|
||
"similar_code_prefix": similarPrefix,
|
||
"search_mode": searchMode,
|
||
})
|
||
return
|
||
}
|
||
|
||
if similarPrefix != "" {
|
||
if mssqlDB != nil {
|
||
rows, err := queries.GetProductionHasCostPurchaseHistoryByCodePrefix(ctx, mssqlDB, similarPrefix, costDate, limit)
|
||
if err != nil {
|
||
logger.Warn("prefix purchase query error", "err", err)
|
||
} else {
|
||
defer rows.Close()
|
||
for rows.Next() {
|
||
var item models.ProductionHasCostDetailPurchaseHistoryRow
|
||
if err := rows.Scan(
|
||
&item.SourceType,
|
||
&item.PriceType,
|
||
&item.Tarih,
|
||
&item.FaturaKodu,
|
||
&item.FirmaKodu,
|
||
&item.FirmaAciklama,
|
||
&item.MasrafKodu,
|
||
&item.MasrafDetay,
|
||
&item.ColorCode,
|
||
&item.ColorDescription,
|
||
&item.ItemDim1Code,
|
||
&item.ItemDim1Description,
|
||
&item.Miktar,
|
||
&item.BIRIM,
|
||
&item.EvrakFiyat,
|
||
&item.EvrakTutar,
|
||
&item.EvrakDoviz,
|
||
); err != nil {
|
||
logger.Warn("prefix purchase scan error", "err", err)
|
||
continue
|
||
}
|
||
response.PurchaseRows = append(response.PurchaseRows, item)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Warn("prefix purchase rows error", "err", err)
|
||
}
|
||
}
|
||
}
|
||
|
||
if uretimDB != nil {
|
||
rows, err := queries.GetProductionHasCostOnMLHistoryByCodePrefix(ctx, uretimDB, similarPrefix, costDate, limit)
|
||
if err != nil {
|
||
logger.Warn("prefix onml query error", "err", err)
|
||
} else {
|
||
defer rows.Close()
|
||
for rows.Next() {
|
||
var item models.ProductionHasCostDetailRecipeHistoryRow
|
||
if err := rows.Scan(
|
||
&item.SourceType,
|
||
&item.PriceType,
|
||
&item.DteIslemTarihi,
|
||
&item.NOnMLNo,
|
||
&item.FirmaKodu,
|
||
&item.FirmaAciklama,
|
||
&item.SKodu,
|
||
&item.SAciklama,
|
||
&item.SRenk,
|
||
&item.LMiktar,
|
||
&item.SBirim,
|
||
&item.LDovizFiyati,
|
||
&item.LDovizTutari,
|
||
&item.USD,
|
||
&item.DUMMY,
|
||
); err != nil {
|
||
logger.Warn("prefix onml scan error", "err", err)
|
||
continue
|
||
}
|
||
response.RecipeRows = append(response.RecipeRows, item)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Warn("prefix onml rows error", "err", err)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if allowRecipeAutoFallback && len(response.RecipeRows) == 0 && nHammaddeTuruNo != "" && uretimDB != nil {
|
||
recipeMatchStage = "hammadde-turu-fallback"
|
||
rows, err := queries.GetProductionHasCostOnMLHistoryByHammaddeTuruNo(ctx, uretimDB, nHammaddeTuruNo, costDate, limit)
|
||
if err != nil {
|
||
logger.Warn("fallback onml query error", "err", err)
|
||
http.Error(w, "Sorgu calistirilirken hata olustu", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
for rows.Next() {
|
||
var item models.ProductionHasCostDetailRecipeHistoryRow
|
||
if err := rows.Scan(
|
||
&item.SourceType,
|
||
&item.PriceType,
|
||
&item.DteIslemTarihi,
|
||
&item.NOnMLNo,
|
||
&item.FirmaKodu,
|
||
&item.FirmaAciklama,
|
||
&item.SKodu,
|
||
&item.SAciklama,
|
||
&item.SRenk,
|
||
&item.LMiktar,
|
||
&item.SBirim,
|
||
&item.LDovizFiyati,
|
||
&item.LDovizTutari,
|
||
&item.USD,
|
||
&item.DUMMY,
|
||
); err != nil {
|
||
logger.Warn("fallback onml scan error", "err", err)
|
||
continue
|
||
}
|
||
response.RecipeRows = append(response.RecipeRows, item)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Warn("fallback onml rows error", "err", err)
|
||
}
|
||
}
|
||
|
||
if len(response.PurchaseRows) == 0 {
|
||
purchaseMatchStage = "empty"
|
||
}
|
||
if len(response.RecipeRows) == 0 {
|
||
recipeMatchStage = "empty"
|
||
}
|
||
|
||
logger.Info("request done",
|
||
"search_mode", searchMode,
|
||
"purchase_match_stage", purchaseMatchStage,
|
||
"recipe_match_stage", recipeMatchStage,
|
||
"similar_prefix", similarPrefix,
|
||
"purchase_count", len(response.PurchaseRows),
|
||
"recipe_count", len(response.RecipeRows),
|
||
)
|
||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||
"purchaseRows": response.PurchaseRows,
|
||
"recipeRows": response.RecipeRows,
|
||
"purchase_match_stage": purchaseMatchStage,
|
||
"recipe_match_stage": recipeMatchStage,
|
||
"similar_code_prefix": similarPrefix,
|
||
"search_mode": searchMode,
|
||
})
|
||
}
|
||
|
||
func parsePositiveIntOrDefault(raw string, fallback int) int {
|
||
v, err := strconv.Atoi(strings.TrimSpace(raw))
|
||
if err != nil || v < 0 {
|
||
return fallback
|
||
}
|
||
return v
|
||
}
|
||
|
||
func normalizeLookupValue(raw string) string {
|
||
return strings.ToUpper(strings.TrimSpace(raw))
|
||
}
|
||
|
||
func firstNonEmptyString(values ...string) string {
|
||
for _, value := range values {
|
||
value = strings.TrimSpace(value)
|
||
if value != "" {
|
||
return value
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// ============================================================
|
||
// MT BOLUM MAPPING (URETIM DB)
|
||
// ============================================================
|
||
|
||
// GET /api/pricing/production-product-costing/options/urun-ana-grup
|
||
func GetProductionProductCostingUrunAnaGrupOptionsHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
mssqlDB := db.GetDB()
|
||
if mssqlDB == nil {
|
||
http.Error(w, "MSSQL veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
search := strings.TrimSpace(r.URL.Query().Get("search"))
|
||
limit := parsePositiveIntOrDefault(r.URL.Query().Get("limit"), 50)
|
||
if limit > 500 {
|
||
limit = 500
|
||
}
|
||
|
||
logger := utils.SlogFromContext(ctx).With(
|
||
"handler", "production-product-costing.options.urun-ana-grup",
|
||
"search", search,
|
||
"limit", limit,
|
||
)
|
||
logger.Info("request start")
|
||
|
||
rows, err := queries.GetProductionProductCostingAnaGrupOptions(ctx, mssqlDB, search, limit)
|
||
if err != nil {
|
||
logger.Error("query error", "err", err)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
out := make([]models.ProductionProductCostingLookupOption, 0, limit)
|
||
for rows.Next() {
|
||
var v string
|
||
if err := rows.Scan(&v); err != nil {
|
||
logger.Warn("scan error", "err", err)
|
||
continue
|
||
}
|
||
v = strings.TrimSpace(v)
|
||
if v == "" {
|
||
continue
|
||
}
|
||
out = append(out, models.ProductionProductCostingLookupOption{Value: v, Label: v})
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Error("rows error", "err", err)
|
||
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
logger.Info("request done", "row_count", len(out))
|
||
_ = json.NewEncoder(w).Encode(out)
|
||
}
|
||
|
||
// GET /api/pricing/production-product-costing/options/urun-alt-grup
|
||
func GetProductionProductCostingUrunAltGrupOptionsHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
mssqlDB := db.GetDB()
|
||
if mssqlDB == nil {
|
||
http.Error(w, "MSSQL veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
urunAnaGrubu := strings.TrimSpace(r.URL.Query().Get("urun_ana_grubu"))
|
||
search := strings.TrimSpace(r.URL.Query().Get("search"))
|
||
limit := parsePositiveIntOrDefault(r.URL.Query().Get("limit"), 50)
|
||
if limit > 500 {
|
||
limit = 500
|
||
}
|
||
|
||
logger := utils.SlogFromContext(ctx).With(
|
||
"handler", "production-product-costing.options.urun-alt-grup",
|
||
"urun_ana_grubu", urunAnaGrubu,
|
||
"search", search,
|
||
"limit", limit,
|
||
)
|
||
logger.Info("request start")
|
||
|
||
rows, err := queries.GetProductionProductCostingAltGrupOptions(ctx, mssqlDB, urunAnaGrubu, search, limit)
|
||
if err != nil {
|
||
logger.Error("query error", "err", err)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
out := make([]models.ProductionProductCostingLookupOption, 0, limit)
|
||
for rows.Next() {
|
||
var v string
|
||
if err := rows.Scan(&v); err != nil {
|
||
logger.Warn("scan error", "err", err)
|
||
continue
|
||
}
|
||
v = strings.TrimSpace(v)
|
||
if v == "" {
|
||
continue
|
||
}
|
||
out = append(out, models.ProductionProductCostingLookupOption{Value: v, Label: v})
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Error("rows error", "err", err)
|
||
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
logger.Info("request done", "row_count", len(out))
|
||
_ = json.NewEncoder(w).Encode(out)
|
||
}
|
||
|
||
// GET /api/pricing/production-product-costing/options/urun-ana-alt-combos
|
||
func GetProductionProductCostingUrunAnaAltCombosHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
mssqlDB := db.GetDB()
|
||
if mssqlDB == nil {
|
||
http.Error(w, "MSSQL veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
search := strings.TrimSpace(r.URL.Query().Get("search"))
|
||
limit := parsePositiveIntOrDefault(r.URL.Query().Get("limit"), 2000)
|
||
if limit > 5000 {
|
||
limit = 5000
|
||
}
|
||
|
||
logger := utils.SlogFromContext(ctx).With(
|
||
"handler", "production-product-costing.options.urun-ana-alt-combos",
|
||
"search", search,
|
||
"limit", limit,
|
||
)
|
||
logger.Info("request start")
|
||
|
||
rows, err := queries.GetProductionProductCostingAnaAltComboRows(ctx, mssqlDB, search, limit)
|
||
if err != nil {
|
||
// Keep the response generic, but log the underlying SQL driver error for diagnostics.
|
||
logger.Error("query error", "err", err, "search", search, "limit", limit, "trace_id", traceID)
|
||
log.Printf("❌ [ProductionProductCostingAnaAltCombos] query error trace_id=%s search=%q limit=%d err=%v", traceID, search, limit, err)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
out := make([]models.ProductionProductCostingAnaAltComboRow, 0, 1024)
|
||
for rows.Next() {
|
||
var item models.ProductionProductCostingAnaAltComboRow
|
||
if err := rows.Scan(&item.UrunIlkGrubu, &item.UrunAnaGrubu, &item.UrunAltGrubu); err != nil {
|
||
logger.Warn("scan error", "err", err)
|
||
continue
|
||
}
|
||
item.UrunIlkGrubu = strings.TrimSpace(item.UrunIlkGrubu)
|
||
item.UrunAnaGrubu = strings.TrimSpace(item.UrunAnaGrubu)
|
||
item.UrunAltGrubu = strings.TrimSpace(item.UrunAltGrubu)
|
||
if item.UrunIlkGrubu == "" || item.UrunAnaGrubu == "" || item.UrunAltGrubu == "" {
|
||
continue
|
||
}
|
||
out = append(out, item)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Error("rows error", "err", err, "trace_id", traceID)
|
||
log.Printf("⚠️ [ProductionProductCostingAnaAltCombos] rows error trace_id=%s err=%v", traceID, err)
|
||
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
logger.Info("request done", "row_count", len(out))
|
||
_ = json.NewEncoder(w).Encode(out)
|
||
}
|
||
|
||
// GET /api/pricing/production-product-costing/options/mtbolum
|
||
func GetProductionProductCostingMTBolumOptionsHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
uretimDB := db.GetUretimDB()
|
||
if uretimDB == nil {
|
||
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
search := strings.TrimSpace(r.URL.Query().Get("search"))
|
||
limit := parsePositiveIntOrDefault(r.URL.Query().Get("limit"), 50)
|
||
if limit > 500 {
|
||
limit = 500
|
||
}
|
||
|
||
logger := utils.SlogFromContext(ctx).With(
|
||
"handler", "production-product-costing.options.mtbolum",
|
||
"search", search,
|
||
"limit", limit,
|
||
)
|
||
logger.Info("request start")
|
||
|
||
rows, err := queries.GetProductionProductCostingMTBolumOptions(ctx, uretimDB, search, limit)
|
||
if err != nil {
|
||
logger.Error("query error", "err", err)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
out := make([]models.ProductionProductCostingLookupOption, 0, limit)
|
||
for rows.Next() {
|
||
var id int
|
||
var name string
|
||
if err := rows.Scan(&id, &name); err != nil {
|
||
logger.Warn("scan error", "err", err)
|
||
continue
|
||
}
|
||
label := strings.TrimSpace(strconv.Itoa(id) + " - " + strings.TrimSpace(name))
|
||
out = append(out, models.ProductionProductCostingLookupOption{
|
||
Value: strconv.Itoa(id),
|
||
Label: label,
|
||
})
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Error("rows error", "err", err)
|
||
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
logger.Info("request done", "row_count", len(out))
|
||
_ = json.NewEncoder(w).Encode(out)
|
||
}
|
||
|
||
// GET /api/pricing/production-product-costing/maliyet-parca-eslestirme
|
||
func GetProductionProductCostingParcaMappingsHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
uretimDB := db.GetUretimDB()
|
||
if uretimDB == nil {
|
||
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
urunIlkGrubu := strings.TrimSpace(r.URL.Query().Get("urun_ilk_grubu"))
|
||
urunAnaGrubu := strings.TrimSpace(r.URL.Query().Get("urun_ana_grubu"))
|
||
urunAltGrubu := strings.TrimSpace(r.URL.Query().Get("urun_alt_grubu"))
|
||
nUrtMTBolumID := parsePositiveIntOrDefault(r.URL.Query().Get("n_urt_mt_bolum_id"), 0)
|
||
rawOnlyActive := strings.TrimSpace(r.URL.Query().Get("only_active"))
|
||
var onlyActive *bool = nil
|
||
if rawOnlyActive != "" {
|
||
v := rawOnlyActive == "1" || strings.EqualFold(rawOnlyActive, "true")
|
||
onlyActive = &v
|
||
}
|
||
|
||
logger := utils.SlogFromContext(ctx).With(
|
||
"handler", "production-product-costing.maliyet-parca-eslestirme.list",
|
||
"urun_ilk_grubu", urunIlkGrubu,
|
||
"urun_ana_grubu", urunAnaGrubu,
|
||
"urun_alt_grubu", urunAltGrubu,
|
||
"n_urt_mt_bolum_id", nUrtMTBolumID,
|
||
"only_active", rawOnlyActive,
|
||
)
|
||
logger.Info("request start")
|
||
|
||
rows, err := queries.ListProductionProductCostingParcaMappings(ctx, uretimDB, urunIlkGrubu, urunAnaGrubu, urunAltGrubu, nUrtMTBolumID, onlyActive)
|
||
if err != nil {
|
||
logger.Error("query error", "err", err)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
out := make([]models.ProductionProductCostingParcaMappingRow, 0, 200)
|
||
for rows.Next() {
|
||
var row models.ProductionProductCostingParcaMappingRow
|
||
var bAktif sql.NullBool
|
||
var hammaddeCsv sql.NullString
|
||
if err := rows.Scan(
|
||
&row.ID,
|
||
&row.UrunIlkGrubu,
|
||
&row.UrunAnaGrubu,
|
||
&row.UrunAltGrubu,
|
||
&row.NUrtMTBolumID,
|
||
&row.ParcaBolumAdi,
|
||
&hammaddeCsv,
|
||
&bAktif,
|
||
&row.DteIslem,
|
||
&row.SKullaniciAdi,
|
||
); err != nil {
|
||
logger.Warn("scan error", "err", err)
|
||
continue
|
||
}
|
||
row.BAktif = bAktif.Valid && bAktif.Bool
|
||
row.NHammaddeTurleri = make([]string, 0, 8)
|
||
if hammaddeCsv.Valid {
|
||
for _, part := range strings.Split(hammaddeCsv.String, ",") {
|
||
part = strings.TrimSpace(part)
|
||
if part != "" {
|
||
row.NHammaddeTurleri = append(row.NHammaddeTurleri, part)
|
||
}
|
||
}
|
||
}
|
||
out = append(out, row)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
logger.Error("rows error", "err", err)
|
||
http.Error(w, "Veritabani satir hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
logger.Info("request done", "row_count", len(out))
|
||
_ = json.NewEncoder(w).Encode(out)
|
||
}
|
||
|
||
// POST /api/pricing/production-product-costing/maliyet-parca-eslestirme/upsert
|
||
func PostProductionProductCostingParcaMappingUpsertHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
uretimDB := db.GetUretimDB()
|
||
if uretimDB == nil {
|
||
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
claims, _ := auth.GetClaimsFromContext(r.Context())
|
||
user := ""
|
||
if claims != nil {
|
||
user = strings.TrimSpace(claims.Username)
|
||
}
|
||
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.maliyet-parca-eslestirme.upsert")
|
||
logger.Info("request start")
|
||
|
||
var req models.ProductionProductCostingParcaMappingUpsertRequest
|
||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
logger.Warn("invalid json", "err", err)
|
||
http.Error(w, "Gecersiz JSON", http.StatusBadRequest)
|
||
return
|
||
}
|
||
req.UrunAnaGrubu = strings.TrimSpace(req.UrunAnaGrubu)
|
||
req.UrunAltGrubu = strings.TrimSpace(req.UrunAltGrubu)
|
||
req.UrunIlkGrubu = strings.TrimSpace(req.UrunIlkGrubu)
|
||
if req.UrunIlkGrubu == "" || req.UrunAnaGrubu == "" || req.UrunAltGrubu == "" || req.NUrtMTBolumID <= 0 {
|
||
http.Error(w, "urunIlkGrubu, urunAnaGrubu, urunAltGrubu ve nUrtMTBolumID zorunlu", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
allowed, invalid, ferr := queries.FilterHammaddeTurleriForPart(ctx, uretimDB, req.NUrtMTBolumID, req.NHammaddeTurleri)
|
||
if ferr != nil {
|
||
logger.Error("hammadde validation error", "err", ferr)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
// Soft-enforce: drop invalid types instead of hard failing. This prevents a "multi-part save" from failing
|
||
// when the UI reuses the same hammadde list across multiple MT bolum selections.
|
||
// We still return the ignored list so UI/logs can surface it.
|
||
ignored := invalid
|
||
|
||
id, err := queries.UpsertProductionProductCostingParcaMapping(ctx, uretimDB, req.UrunIlkGrubu, req.UrunAnaGrubu, req.UrunAltGrubu, req.NUrtMTBolumID, allowed, req.BAktif, user)
|
||
if err != nil {
|
||
logger.Error("exec error", "err", err)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
logger.Info("request done", "id", id, "user", user, "bAktif", req.BAktif)
|
||
if len(ignored) > 0 {
|
||
logger.Warn("hammadde types ignored due to MT bolum mismatch", "nUrtMTBolumID", req.NUrtMTBolumID, "ignored_count", len(ignored))
|
||
}
|
||
_ = json.NewEncoder(w).Encode(map[string]any{"ok": true, "id": id, "ignored": ignored})
|
||
}
|
||
|
||
type productionProductCostingSetActiveRequest struct {
|
||
ID int `json:"id"`
|
||
BAktif bool `json:"bAktif"`
|
||
}
|
||
|
||
// POST /api/pricing/production-product-costing/maliyet-parca-eslestirme/set-active
|
||
func PostProductionProductCostingParcaMappingSetActiveHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
uretimDB := db.GetUretimDB()
|
||
if uretimDB == nil {
|
||
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
claims, _ := auth.GetClaimsFromContext(r.Context())
|
||
user := ""
|
||
if claims != nil {
|
||
user = strings.TrimSpace(claims.Username)
|
||
}
|
||
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.maliyet-parca-eslestirme.set-active")
|
||
logger.Info("request start")
|
||
|
||
var req productionProductCostingSetActiveRequest
|
||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
logger.Warn("invalid json", "err", err)
|
||
http.Error(w, "Gecersiz JSON", http.StatusBadRequest)
|
||
return
|
||
}
|
||
if req.ID <= 0 {
|
||
http.Error(w, "id zorunlu", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
if err := queries.SetProductionProductCostingParcaMappingActive(ctx, uretimDB, req.ID, req.BAktif, user); err != nil {
|
||
logger.Error("exec error", "err", err)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
logger.Info("request done", "id", req.ID, "bAktif", req.BAktif, "user", user)
|
||
_ = json.NewEncoder(w).Encode(map[string]any{"ok": true})
|
||
}
|
||
|
||
// DELETE /api/pricing/production-product-costing/maliyet-parca-eslestirme?id=123
|
||
func DeleteProductionProductCostingParcaMappingHandler(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
uretimDB := db.GetUretimDB()
|
||
if uretimDB == nil {
|
||
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
|
||
return
|
||
}
|
||
|
||
traceID := utils.TraceIDFromRequest(r)
|
||
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
||
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.maliyet-parca-eslestirme.delete")
|
||
logger.Info("request start")
|
||
|
||
id := parsePositiveIntOrDefault(r.URL.Query().Get("id"), 0)
|
||
if id <= 0 {
|
||
http.Error(w, "id zorunlu", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
if err := queries.DeleteProductionProductCostingParcaMapping(ctx, uretimDB, id); err != nil {
|
||
logger.Error("exec error", "err", err)
|
||
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
logger.Info("request done", "id", id)
|
||
_ = json.NewEncoder(w).Encode(map[string]any{"ok": true})
|
||
}
|