@@ -327,6 +327,9 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
groupName string
groupTotal float64
groupTotalUSD float64
nOnMLNoStr string
nOnMLDetNoStr string
hNoStr string
fiyatGirilen sql . NullFloat64
fiyatDoviz sql . NullString
maliyeteDahil sql . NullBool
@@ -338,9 +341,9 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
& groupName ,
& groupTotal ,
& groupTotalUSD ,
& item . NOnMLNo ,
& item . NOnMLDetNo ,
& item . NHammaddeTuruNo ,
& nOnMLNoStr ,
& nOnMLDetNoStr ,
& hNoStr ,
& item . SKodu ,
& item . SAciklama ,
& item . SRenk ,
@@ -371,6 +374,10 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
}
scannedRows += 1
item . NOnMLNo , _ = strconv . Atoi ( nOnMLNoStr )
item . NOnMLDetNo , _ = strconv . Atoi ( nOnMLDetNoStr )
item . NHammaddeTuruNo , _ = strconv . Atoi ( hNoStr )
if fiyatGirilen . Valid {
item . FiyatGirilen = new ( float64 )
* item . FiyatGirilen = fiyatGirilen . Float64
@@ -426,8 +433,8 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
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 )
log . Printf ( "❌ [ProductionHasCostDetailGroups] query error: %v" , err )
http . Error ( w , "Veritabanı hatası " , http . StatusInternalServerError )
return
}
defer rows . Close ( )
@@ -442,6 +449,9 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
groupName string
groupTotal float64
groupTotalUSD float64
nOnMLNoStr string
nOnMLDetNoStr string
hNoStr string
fiyatGirilen sql . NullFloat64
fiyatDoviz sql . NullString
maliyeteDahil sql . NullBool
@@ -453,9 +463,9 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
& groupName ,
& groupTotal ,
& groupTotalUSD ,
& item . NOnMLNo ,
& item . NOnMLDetNo ,
& item . NHammaddeTuruNo ,
& nOnMLNoStr ,
& nOnMLDetNoStr ,
& hNoStr ,
& item . SKodu ,
& item . SAciklama ,
& item . SRenk ,
@@ -479,10 +489,16 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
& item . SHammaddeTuruAdi ,
& item . SParcaAdi ,
) ; err != nil {
log . Printf ( "âš ï¸ [ProductionHasCostDetailGroups] scan error: %v" , err )
scanErrors ++
logger . Warn ( "scan error" , "scan_index" , scannedRows + scanErrors , "err" , err )
log . Printf ( "⚠️ [ProductionHasCostDetailGroups] scan error: %v" , err )
continue
}
scannedRows += 1
scannedRows ++
item . NOnMLNo , _ = strconv . Atoi ( nOnMLNoStr )
item . NOnMLDetNo , _ = strconv . Atoi ( nOnMLDetNoStr )
item . NHammaddeTuruNo , _ = strconv . Atoi ( hNoStr )
if fiyatGirilen . Valid {
item . FiyatGirilen = new ( float64 )
@@ -1093,21 +1109,74 @@ func PostProductionProductCostingDefaultQuantitiesLookupHandler(w http.ResponseW
return
}
m , err := queries . LookupProductionProductCostingDefaultQtyByNos ( ctx , uretimDB , req . NHammaddeTuruNos )
rows , err := queries . LookupProductionProductCostingDefaultQtyByNos ( ctx , uretimDB , req . NHammaddeTuruNos )
if err != nil {
http . Error ( w , "Veritabani hatasi" , http . StatusInternalServerError )
return
}
out := make ( [ ] models . ProductionProductCostingDefaultQtyLookupItem , 0 , len ( m ) )
for no , qty := range m {
out := make ( [ ] models . ProductionProductCostingDefaultQtyLookupItem , 0 , len ( rows ) )
for _ , r0 := range rows {
out = append ( out , models . ProductionProductCostingDefaultQtyLookupItem {
NHammaddeTuruNo : no ,
LDefaultMiktar : qty ,
NHammaddeTuruNo : r0 . No ,
SAciklama : r0 . Aciklama ,
LDefaultMiktar : r0 . Qty ,
} )
}
_ = json . NewEncoder ( w ) . Encode ( out )
}
// POST /api/pricing/production-product-costing/has-cost-detail/last-detail
func PostProductionProductCostingHasCostDetailLastDetailHandler ( 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 )
var req models . ProductionProductCostingLastOnMLDetLookupRequest
if err := json . NewDecoder ( r . Body ) . Decode ( & req ) ; err != nil {
http . Error ( w , "Gecersiz JSON" , http . StatusBadRequest )
return
}
rows , err := queries . LookupLastOnMLMasDetByHammaddeNos ( ctx , uretimDB , req . NHammaddeTuruNos , req . BeforeDate , req . ExcludeOnMLNo , req . NFirmaID , req . OnlyICode , 1 )
if err != nil {
http . Error ( w , "Veritabani hatasi" , http . StatusInternalServerError )
return
}
_ = json . NewEncoder ( w ) . Encode ( rows )
}
// POST /api/pricing/production-product-costing/options/hammadde-by-nos
func PostProductionProductCostingOptionsHammaddeByNosHandler ( 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 )
var req models . ProductionProductCostingHammaddeByNosRequest
if err := json . NewDecoder ( r . Body ) . Decode ( & req ) ; err != nil {
http . Error ( w , "Gecersiz JSON" , http . StatusBadRequest )
return
}
rows , err := queries . GetProductionHammaddeByNos ( ctx , uretimDB , req . NHammaddeTuruNos )
if err != nil {
http . Error ( w , "Veritabani hatasi" , http . StatusInternalServerError )
return
}
_ = json . NewEncoder ( w ) . Encode ( rows )
}
// POST /api/pricing/production-product-costing/default-quantities/refresh
func PostProductionProductCostingDefaultQuantitiesRefreshHandler ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Content-Type" , "application/json; charset=utf-8" )
@@ -1166,6 +1235,11 @@ func PostProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http.
req . Header . FirmaKodu = strings . TrimSpace ( req . Header . FirmaKodu )
if req . Header . UrunKodu == "" || req . Header . MaliyetTarihi == "" {
logger . Warn ( "validation failed: missing required header fields" ,
"trace_id" , traceID ,
"urun_kodu" , req . Header . UrunKodu ,
"maliyet_tarihi" , req . Header . MaliyetTarihi ,
)
http . Error ( w , "urun_kodu ve maliyet_tarihi zorunlu" , http . StatusBadRequest )
return
}
@@ -1210,6 +1284,11 @@ func PostProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http.
firmaID = id
}
if firmaID <= 0 {
logger . Warn ( "validation failed: missing firma" ,
"trace_id" , traceID ,
"n_firma_id" , req . Header . NFirmaID ,
"firma_kodu" , req . Header . FirmaKodu ,
)
http . Error ( w , "Firma secilmeden kaydedilemez (n_firma_id / firma_kodu)" , http . StatusBadRequest )
return
}
@@ -1264,6 +1343,11 @@ func PostProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http.
// Parse Tarihi
tarihi , err := time . Parse ( "2006-01-02" , req . Header . MaliyetTarihi )
if err != nil {
logger . Warn ( "validation failed: maliyet_tarihi parse error" ,
"trace_id" , traceID ,
"maliyet_tarihi" , req . Header . MaliyetTarihi ,
"err" , err ,
)
http . Error ( w , "maliyet_tarihi YYYY-MM-DD formatinda olmali" , http . StatusBadRequest )
return
}
@@ -1274,7 +1358,23 @@ func PostProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http.
http . Error ( w , "Islem baslatilamadi" , http . StatusInternalServerError )
return
}
defer func ( ) { _ = tx . Rollback ( ) } ( )
committed := false
logger . Info ( "tx begin" ,
"trace_id" , traceID ,
"n_onml_no" , nOnMLNo ,
"urun_kodu" , req . Header . UrunKodu ,
"maliyet_tarihi" , req . Header . MaliyetTarihi ,
)
defer func ( ) {
if committed {
return
}
if err := tx . Rollback ( ) ; err == nil {
logger . Info ( "tx rollback" , "trace_id" , traceID , "n_onml_no" , nOnMLNo )
} else {
logger . Warn ( "tx rollback failed" , "trace_id" , traceID , "n_onml_no" , nOnMLNo , "err" , err )
}
} ( )
// Determine mamul turu inside same tx (to keep create atomic)
mamulLabel := ""
@@ -1299,6 +1399,7 @@ func PostProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http.
return
}
if mt <= 0 {
logger . Warn ( "validation failed: mamul turu not found" , "trace_id" , traceID , "n_onml_no" , nOnMLNo , "mamul_label" , mamulLabel )
http . Error ( w , "Mamul turu bulunamadi: " + mamulLabel , http . StatusBadRequest )
return
}
@@ -1320,6 +1421,7 @@ func PostProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http.
sAciklama = sql . NullString { String : req . Header . SAciklama , Valid : true }
}
logger . Info ( "tx step" , "trace_id" , traceID , "n_onml_no" , nOnMLNo , "step" , "upsert_header" )
if err := queries . UpsertOnMLHeader ( tx , ctx , queries . OnMLHeaderUpsertArgs {
NOnMLNo : nOnMLNo ,
UrunKodu : req . Header . UrunKodu ,
@@ -1343,6 +1445,7 @@ func PostProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http.
}
// Deletes
logger . Info ( "tx step" , "trace_id" , traceID , "n_onml_no" , nOnMLNo , "step" , "detail_deletes" , "count" , len ( req . Detail . Deletes ) )
for _ , d := range req . Detail . Deletes {
if d . NOnMLDetNo <= 0 {
continue
@@ -1355,13 +1458,33 @@ func PostProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http.
}
// Upserts
logger . Info ( "tx step" , "trace_id" , traceID , "n_onml_no" , nOnMLNo , "step" , "detail_upserts" , "count" , len ( req . Detail . Upserts ) )
for _ , row := range req . Detail . Upserts {
if row . NOnMLDetNo <= 0 {
continue
}
if row . NHammaddeTuruNo <= 0 || strings . TrimSpace ( row . SKodu ) == "" {
http . Error ( w , "Detay satirinda n_h ammadde_t uru_no ve s_kodu zorunlu" , http . StatusBadRequest )
return
// FALLBACK: If nH ammaddeT uruNo is missing but sKodu is present, default to 1 (General/Labor)
// to avoid blocking the user, especially for labor items.
if strings . TrimSpace ( row . SKodu ) != "" && row . NHammaddeTuruNo <= 0 {
logger . Warn ( "n_hammadde_turu_no <= 0, falling back to 1" ,
"trace_id" , traceID ,
"n_onml_no" , nOnMLNo ,
"n_onml_det_no" , row . NOnMLDetNo ,
"s_kodu" , strings . TrimSpace ( row . SKodu ) ,
)
row . NHammaddeTuruNo = 1
} else {
logger . Warn ( "validation failed: missing required detail fields (s_kodu empty)" ,
"trace_id" , traceID ,
"n_onml_no" , nOnMLNo ,
"n_onml_det_no" , row . NOnMLDetNo ,
"n_hammadde_turu_no" , row . NHammaddeTuruNo ,
"s_kodu" , strings . TrimSpace ( row . SKodu ) ,
)
http . Error ( w , "Detay satirinda s_kodu zorunlu" , http . StatusBadRequest )
return
}
}
qty := row . LMiktar
if qty < 0 {
@@ -1387,9 +1510,21 @@ func PostProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http.
lTutar := unitTRY * qty
lDovizTutari := unitUSD * qty
// Debug log for price resolution
logger . Info ( "price debug" ,
"s_kodu" , strings . TrimSpace ( row . SKodu ) ,
"qty" , qty ,
"fiyat_girilen" , row . FiyatGirilen ,
"fiyat_doviz" , strings . TrimSpace ( row . FiyatDoviz ) ,
"unitTRY" , unitTRY ,
"lTutar" , lTutar ,
"lDovizTutari" , lDovizTutari ,
)
// Resolve stock type id from tbStok by sKodu (exact), then fallback to model-based match.
// Note: In this DB, stock type is stored as tbStok.nStokTipi but spUrtOnMLMasDet expects nStokTipiID (int).
rawSKodu := strings . TrimSpace ( row . SKodu )
logger . Info ( "resolving stock type" , "s_kodu" , rawSKodu )
var nStokTipiID int
err := tx . QueryRowContext ( ctx , `
SELECT TOP 1 ISNULL(CONVERT(int, ISNULL(S.nStokTipi, 0)), 0) AS nStokTipiID
@@ -1411,16 +1546,32 @@ ORDER BY
` , rawSKodu ) . Scan ( & nStokTipiID )
if err != nil {
if err == sql . ErrNoRows {
http . Error ( w , "S tok tipi bulunamadi (s_kodu=" + rawSKodu + ")" , http . StatusBadRequest )
// FALLBACK: If s toc k item not found in tbStok at all, default to 1.
logger . Warn ( "stok tipi not found in tbStok, falling back to 1" ,
"trace_id" , traceID ,
"n_onml_no" , nOnMLNo ,
"n_onml_det_no" , row . NOnMLDetNo ,
"s_kodu" , rawSKodu ,
)
nStokTipiID = 1
} else {
logger . Error ( "stok tipi lookup error" , "err" , err )
http . Error ( w , "Stok tipi bulunamadi (tbStok sorgu hatasi)" , http . StatusInternalServerError )
return
}
logger . Error ( "stok tipi lookup error" , "err" , err )
http . Error ( w , "Stok tipi bulunamadi (tbStok sorgu hatasi)" , http . StatusInternalServerError )
return
}
logger . Info ( "stock type resolved" , "s_kodu" , rawSKodu , "n_stok_tipi_id" , nStokTipiID )
if nStokTipiID <= 0 {
http . Error ( w , "S tok tipi bulunamadi (s_kodu=" + rawSKodu + ")" , http . StatusBadRequest )
return
// FALLBACK: If s toc k type is missing or 0 in tbStok, default to 1 (usually 'Raw Material' or 'General').
// This prevents blocking the save process for items not fully configured in tbStok.
logger . Warn ( "stok tipi <= 0, falling back to 1" ,
"trace_id" , traceID ,
"n_onml_no" , nOnMLNo ,
"n_onml_det_no" , row . NOnMLDetNo ,
"s_kodu" , rawSKodu ,
"n_stok_tipi_id_orig" , nStokTipiID ,
)
nStokTipiID = 1
}
// Dummy/system-mapped required fields:
@@ -1469,7 +1620,8 @@ WHEN MATCHED THEN
Maliyete_dahil = @p29,
cm_price_type_id = @p30,
sKullaniciAdiDeg = @p31,
dteIslemTarihiDeg = GETDATE()
dteIslemTarihiDeg = GETDATE(),
sAciklama3 = NULLIF(@p32, '')
WHEN NOT MATCHED THEN
INSERT (
nOnMLNo,nOnMLDetNo,nHammaddeTuruNo,sKodu,sAciklama,sRenk,lMiktar,lFiyat,lTutar,
@@ -1480,7 +1632,7 @@ WHEN NOT MATCHED THEN
VALUES (
@p1,@p2,@p3,@p4,@p5,@p6,@p9,@p10,@p11,
@p12,@p13,@p14,@p15,@p16,@p17,@p18,@p19,
@p20,NULLIF(@p8,''),@p21,@p22,@p31,GETDATE(),NULLIF(@p7,''),NULL,@p23,@p24,
@p20,NULLIF(@p8,''),@p21,@p22,@p31,GETDATE(),NULLIF(@p7,''),NULLIF(@p32, '') ,@p23,@p24,
@p25,@p26,NULLIF(@p27,0),NULLIF(@p28,''),@p29,@p30
);
`
@@ -1519,8 +1671,21 @@ WHEN NOT MATCHED THEN
row . MaliyeteDahil ,
row . CMPriceTypeID ,
user ,
strings . TrimSpace ( row . SAciklama3 ) , // p32: sAciklama3 (Grup Adi)
) ; err != nil {
logger . Error ( "detail merge error" , "err" , err )
logger . Error ( "detail merge error" ,
"trace_id" , traceID ,
"n_onml_no" , nOnMLNo ,
"n_onml_det_no" , row . NOnMLDetNo ,
"n_hammadde_turu_no" , row . NHammaddeTuruNo ,
"n_urt_mt_bolum_id" , row . NUrtMTBolumID ,
"s_kodu" , strings . TrimSpace ( row . SKodu ) ,
"fiyat_girilen" , row . FiyatGirilen ,
"fiyat_doviz" , strings . TrimSpace ( row . FiyatDoviz ) ,
"l_miktar" , qty ,
"n_stok_tipi_id" , nStokTipiID ,
"err" , err ,
)
http . Error ( w , "Detay kaydedilemedi" , http . StatusInternalServerError )
return
}
@@ -1533,6 +1698,7 @@ WHEN NOT MATCHED THEN
// ============================================================
if req . Header . NUrtReceteID > 0 {
receteID := req . Header . NUrtReceteID
logger . Info ( "tx step" , "trace_id" , traceID , "n_onml_no" , nOnMLNo , "step" , "recipe_sync" , "n_urt_recete_id" , receteID )
// Determine next available recipe detail id (nUrtRecMBolumID)
nextRecDetID := 0
@@ -1551,94 +1717,72 @@ WHERE RMik.nUrtReceteID = @p1
continue
}
// CM1/CM2 rows are cost-only and must NOT be written back into recipe tables.
// Source of truth: spUrtOnMLHammaddeTuru.sAciklama3 (e.g. 'CM2') .
var grp sql . NullString
if err : = tx . QueryRowContext ( ctx , `
SELECT TOP 1 LTRIM(RTRIM(ISNULL(H.sAciklama3, '')))
FROM dbo.spUrtOnMLHammaddeTuru H WITH (NOLOCK)
WHERE H.nHammaddeTuruNo = @p1
` , hNo ) . Scan ( & grp ) ; err == nil {
g := strings . ToUpper ( strings . TrimSpace ( grp . String ) )
if g == "CM1" || g == "CM2" {
continue
}
// 1. FILTER: CM1/CM2 (Labor/Service) rows must NOT be written back into recipe tables.
// We check the group label (sAciklama3) from the row itself .
g := strings . ToUpper ( strings . TrimSpace ( row . SAciklama3 ) )
if g = = "CM1" || g == "CM2" {
logger . Info ( "recipe sync skip: labor item" , "s_kodu" , row . SKodu , "group" , g )
continue
}
// Resolve nHStokID from tbStok using sKodu (exact), then sModel fallback.
// IMPORTANT: nHStokID must be resolved; otherwise we'd update/insert too broadly .
// In this version of URETIM DB, the table name is dbo.spUrtRecMBolumMik
// but it uses _G suffixes for quantity and amount columns .
// Also nHStokID_G stores the stock code string rather than just an ID.
rawSKodu := strings . TrimSpace ( row . SKodu )
var nHStokID int
err := tx . QueryRowContext ( ctx , `
SELECT TOP 1 ISNULL(S.nStokID, 0)
FROM dbo.tbStok S WITH (NOLOCK)
WHERE ISNULL(S.IsBlocked, 0) = 0
AND (
REPLACE(LTRIM(RTRIM(ISNULL(S.sKodu,''))), ' ', '') = REPLACE(@p1, ' ', '')
OR LTRIM(RTRIM(ISNULL(S.sModel,''))) = @p1
OR @p1 LIKE LTRIM(RTRIM(ISNULL(S.sModel,''))) + '%'
)
ORDER BY
CASE
WHEN REPLACE(LTRIM(RTRIM(ISNULL(S.sKodu,''))), ' ', '') = REPLACE(@p1, ' ', '') THEN 0
WHEN LTRIM(RTRIM(ISNULL(S.sModel,''))) = @p1 THEN 1
ELSE 2
END,
S.dteKayitTarihi DESC,
S.nStokID DESC
` , rawSKodu ) . Scan ( & nHStokID )
if err != nil {
if err == sql . ErrNoRows {
http . Error ( w , "Recete sync icin stok bulunamadi (s_kodu=" + rawSKodu + ")" , http . StatusBadRequest )
return
}
logger . Error ( "recipe stok lookup error" , "err" , err )
http . Error ( w , "Recete sync stok bulunamadi" , http . StatusInternalServerError )
return
}
if nHStokID == 0 {
http . Error ( w , "Recete sync icin stok bulunamadi (s_kodu=" + rawSKodu + ")" , http . StatusBadRequest )
return
// Ensure a section entry (spUrtRecMBolum) exists for this hNo (Hammadde Turu)
// in the current recipe, otherwise detail rows (Mik) cannot be linked properly.
var sectionExists int
_ = tx . QueryRowContext ( ctx , `
SELECT COUNT(1) FROM dbo.spUrtRecMBolum WITH (NOLOCK)
WHERE nUrtReceteID = @p1 AND nUrtMBolumID = @p2
` , receteID , hNo ) . Scan ( & sectionExists )
if sectionExists <= 0 {
logger . Info ( "creating missing recipe section" , "n_urt_recete_id" , receteID , "n_urt_m_bolum_id" , hNo )
_ , _ = tx . ExecContext ( ctx , `
INSERT INTO dbo.spUrtRecMBolum (nUrtReceteID, nUrtUBolumID, nUrtMBolumID, nUrtMTBolumID, sKullaniciAdi, dteIslemTarihi)
VALUES (@p1, 13, @p2, @p3, @p4, GETDATE())
` , receteID , hNo , row . NUrtMTBolumID , user )
}
// If we cannot resolve stock, we still try to insert by hammadde no only.
// Some recipes may omit nHStokID; but we prefer filling it when possible .
// Update quantity if row already exists for (recete, hammadde, stok)
// Update quantity and prices if row already exists for (recete, hammadde, stok_code)
// Using nHStokID_G (string code) for matching as per user screenshot .
var exists int
if err := tx . QueryRowContext ( ctx , `
SELECT COUNT(1)
FROM dbo.spUrtRecMBolumMik RMik WITH (NOLOCK)
WHERE RMik.nUrtReceteID = @p1
AND RMik.nUrtMBolumID = @p2
AND RMik.nHStokID = @p3
` , receteID , hNo , nHStokID ) . Scan ( & exists ) ; err == nil && exists > 0 {
AND LTRIM(RTRIM( RMik.nHStokID_G)) = @p3
` , receteID , hNo , rawSKodu ) . Scan ( & exists ) ; err == nil && exists > 0 {
_ , _ = tx . ExecContext ( ctx , `
UPDATE dbo.spUrtRecMBolumMik
SET lHMiktar = @p4,
sKullaniciAdiDeg = @p5,
dteIslemTarihiDeg = GETDATE()
SET lHMiktar_G = @p4,
lHMaliyet_G = @p5,
sKullaniciAdiDeg = @p6,
dteIslemTarihiDeg = GETDATE(),
bIslem = @p7
WHERE nUrtReceteID = @p1
AND nUrtMBolumID = @p2
AND nHStokID = @p3
` , receteID , hNo , nHStokID , row . LMiktar , user )
AND LTRIM(RTRIM( nHStokID_G)) = @p3
` , receteID , hNo , rawSKodu , row . LMiktar , unitTRY , user , row . MaliyeteDahil )
continue
}
// Insert missing: best-effort with minimal columns .
// NOTE: This assumes nUrtRecMBolumID can be a sequential int and that other columns are nullable/have defaults.
// Insert missing: using _G columns and storing code in nHStokID_G .
_ , insertErr := tx . ExecContext ( ctx , `
INSERT INTO dbo.spUrtRecMBolumMik (
nUrtReceteID,
nUrtUBolumID,
nUrtRecMBolumID,
nStokID,
nHStokID,
lHMiktar,
lHFire,
nHStokID_G ,
lHMiktar_G ,
lHFire_G ,
nMaliyetTipiID,
lHMaliyet,
lMiktar,
lHMaliyet_G ,
lMiktar_G ,
sIslemKodu,
nUrtMBolumID,
nUrtMTBolumID,
@@ -1646,56 +1790,52 @@ INSERT INTO dbo.spUrtRecMBolumMik (
bIslem,
nSure,
sAciklama,
dteDovizTarihi,
sDovizCinsi,
lDovizOran,
lDovizFiyat,
sKullaniciAdi,
dteIslemTarihi,
nMBolumSarfTipiNo
)
VALUES (
@p1,
0 ,
13 ,
@p2,
0,
@p3,
@p4,
@p3, -- nHStokID_G (Code)
@p4, -- lHMiktar_G
0,
6,
0,
1,
@p5, -- lHMaliyet_G
1, -- lMiktar_G
'',
@p5,
@p6,
1,
0,
0,
NULL,
NULL,
NULL,
NULL,
NULL,
@p7,
1,
@p8, -- bIslem
0,
NULL,
@p9,
GETDATE(),
1
)
` , receteID , nextRecDetID , nHStokID , row . LMiktar , hNo , row . NUrtMTBolumID , user )
` , receteID , nextRecDetID , rawSKodu , row . LMiktar , unitTRY , hNo , row . NUrtMTBolumID , row . MaliyeteDahil , user )
if insertErr == nil {
nextRecDetID += 1
}
}
}
logger . Info ( "tx step" , "trace_id" , traceID , "n_onml_no" , nOnMLNo , "step" , "commit" )
if err := tx . Commit ( ) ; err != nil {
logger . Error ( "tx commit error" , "err" , err )
http . Error ( w , "Kaydetme tamamlanamadi" , http . StatusInternalServerError )
return
}
committed = true
logger . Info ( "tx commit ok" , "trace_id" , traceID , "n_onml_no" , nOnMLNo )
// V3: update base price table so pricing screens reflect latest costing.
// Not transactional with URETIM DB; if this fails, URETIM save has already succeeded.
if mssqlDB != nil {
logger . Info ( "post-commit step" , "trace_id" , traceID , "n_onml_no" , nOnMLNo , "step" , "v3_base_price_upsert" )
if err := queries . UpsertV3ItemBasePriceUSD ( ctx , mssqlDB , req . Header . UrunKodu , req . Header . MaliyetTarihi , totalUSD , user ) ; err != nil {
logger . Error ( "v3 base price upsert error" , "err" , err )
http . Error ( w , "URETIM kaydedildi ama V3 maliyet guncellenemedi" , http . StatusInternalServerError )
@@ -1706,6 +1846,131 @@ VALUES (
_ = json . NewEncoder ( w ) . Encode ( models . ProductionProductCostingOnMLSaveResponse { NOnMLNo : nOnMLNo } )
}
// POST /api/pricing/production-product-costing/onml/delete
// Deletes costing records created in URETIM (OnML header + details) and, if created by this app, V3 base price row.
// IMPORTANT: Recipe tables are NOT touched.
func PostProductionProductCostingOnMLDeleteHandler ( 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 ( )
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.onml.delete" )
var req models . ProductionProductCostingOnMLDeleteRequest
if err := json . NewDecoder ( r . Body ) . Decode ( & req ) ; err != nil {
http . Error ( w , "Gecersiz JSON" , http . StatusBadRequest )
return
}
if req . NOnMLNo <= 0 {
http . Error ( w , "n_onml_no zorunlu" , http . StatusBadRequest )
return
}
// Load header info for safe deletes and redirect behavior.
var urunKodu string
var maliyetTarihi time . Time
err := uretimDB . QueryRowContext ( ctx , `
SELECT TOP 1
LTRIM(RTRIM(ISNULL(UrunKodu,''))) AS UrunKodu,
COALESCE(Tarihi, dteKayitTarihi, GETDATE()) AS Tarihi
FROM dbo.spUrtOnMLMas WITH (NOLOCK)
WHERE nOnMLNo = @p1
` , req . NOnMLNo ) . Scan ( & urunKodu , & maliyetTarihi )
if err != nil {
if err == sql . ErrNoRows {
http . Error ( w , "Kayit bulunamadi" , http . StatusNotFound )
return
}
logger . Error ( "header lookup error" , "err" , err )
http . Error ( w , "Veritabani hatasi" , http . StatusInternalServerError )
return
}
urunKodu = strings . TrimSpace ( urunKodu )
tx , err := uretimDB . BeginTx ( ctx , nil )
if err != nil {
http . Error ( w , "Veritabani hatasi" , http . StatusInternalServerError )
return
}
defer func ( ) { _ = tx . Rollback ( ) } ( )
// Delete details first, then header.
if _ , err := tx . ExecContext ( ctx , `
DELETE FROM dbo.spUrtOnMLMasDet WHERE nOnMLNo = @p1
` , req . NOnMLNo ) ; err != nil {
logger . Error ( "delete detail error" , "err" , err )
http . Error ( w , "Detay silinemedi" , http . StatusInternalServerError )
return
}
if _ , err := tx . ExecContext ( ctx , `
DELETE FROM dbo.spUrtOnMLMas WHERE nOnMLNo = @p1
` , req . NOnMLNo ) ; err != nil {
logger . Error ( "delete header error" , "err" , err )
http . Error ( w , "Header silinemedi" , http . StatusInternalServerError )
return
}
if err := tx . Commit ( ) ; err != nil {
logger . Error ( "tx commit error" , "err" , err )
http . Error ( w , "Silme tamamlanamadi" , http . StatusInternalServerError )
return
}
// V3: Delete base price ONLY if row was created by this app (CreatedUserName starts with BSSAPP).
deletedBasePrice := false
if mssqlDB != nil && urunKodu != "" {
var createdBy sql . NullString
var lastBy sql . NullString
_ = mssqlDB . QueryRowContext ( ctx , `
SELECT TOP 1
ISNULL(CreatedUserName,'') AS CreatedUserName,
ISNULL(LastUpdatedUserName,'') AS LastUpdatedUserName
FROM dbo.prItemBasePrice WITH (NOLOCK)
WHERE ItemTypeCode = 1
AND LTRIM(RTRIM(ItemCode)) = @p1
AND ISNULL(CountryCode,'') = 'TR'
AND ISNULL(SeasonCode,'') = ''
AND ISNULL(BasePriceCode,0) = 1
` , urunKodu ) . Scan ( & createdBy , & lastBy )
created := strings . ToUpper ( strings . TrimSpace ( createdBy . String ) )
last := strings . ToUpper ( strings . TrimSpace ( lastBy . String ) )
if strings . HasPrefix ( created , "BSSAPP" ) && strings . HasPrefix ( last , "BSSAPP" ) {
if _ , err := mssqlDB . ExecContext ( ctx , `
DELETE FROM dbo.prItemBasePrice
WHERE ItemTypeCode = 1
AND LTRIM(RTRIM(ItemCode)) = @p1
AND ISNULL(CountryCode,'') = 'TR'
AND ISNULL(SeasonCode,'') = ''
AND ISNULL(BasePriceCode,0) = 1
` , urunKodu ) ; err == nil {
deletedBasePrice = true
}
}
}
logger . Info ( "delete done" , "n_onml_no" , req . NOnMLNo , "urun_kodu" , urunKodu , "deleted_base_price" , deletedBasePrice , "user" , user )
_ = json . NewEncoder ( w ) . Encode ( map [ string ] any {
"ok" : true ,
"n_onml_no" : req . NOnMLNo ,
"urun_kodu" : urunKodu ,
"deleted_baseprice" : deletedBasePrice ,
} )
}
// 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" )