Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-05-20 21:24:17 +03:00
parent c1c1ed99c7
commit c46a934bc9
5 changed files with 526 additions and 48 deletions

View File

@@ -330,6 +330,7 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
nOnMLNoStr string
nOnMLDetNoStr string
hNoStr string
mtBolumStr string
fiyatGirilen sql.NullFloat64
fiyatDoviz sql.NullString
maliyeteDahil sql.NullBool
@@ -344,6 +345,7 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
&nOnMLNoStr,
&nOnMLDetNoStr,
&hNoStr,
&mtBolumStr,
&item.SKodu,
&item.SAciklama,
&item.SRenk,
@@ -378,6 +380,7 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
item.NOnMLNo = strings.TrimSpace(nOnMLNoStr)
item.NOnMLDetNo = strings.TrimSpace(nOnMLDetNoStr)
item.NHammaddeTuruNo = strings.TrimSpace(hNoStr)
item.NUrtMTBolumID = strings.TrimSpace(mtBolumStr)
if fiyatGirilen.Valid {
item.FiyatGirilen = new(float64)
@@ -453,6 +456,7 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
nOnMLNoStr string
nOnMLDetNoStr string
hNoStr string
mtBolumStr string
fiyatGirilen sql.NullFloat64
fiyatDoviz sql.NullString
maliyeteDahil sql.NullBool
@@ -467,6 +471,7 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
&nOnMLNoStr,
&nOnMLDetNoStr,
&hNoStr,
&mtBolumStr,
&item.SKodu,
&item.SAciklama,
&item.SRenk,
@@ -501,6 +506,7 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
item.NOnMLNo = strings.TrimSpace(nOnMLNoStr)
item.NOnMLDetNo = strings.TrimSpace(nOnMLDetNoStr)
item.NHammaddeTuruNo = strings.TrimSpace(hNoStr)
item.NUrtMTBolumID = strings.TrimSpace(mtBolumStr)
if fiyatGirilen.Valid {
item.FiyatGirilen = new(float64)
@@ -1497,6 +1503,14 @@ func PostProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http.
logger.Info("tx step", "trace_id", traceID, "n_onml_no", nOnMLNo, "step", "detail_upserts", "count", len(req.Detail.Upserts))
skippedUpserts := 0
skippedUpsertsSample := 0
// Cache hammadde_turu -> mt_bolum_id so we don't query master table for every row.
mtBolumByHammadde := map[int]int{}
// Collect source rows for recipe sync (variantless, non-CM2 only).
type recipeKey struct {
nUrtMBolumID int
sKodu string
}
recipeQtyByKey := map[recipeKey]float64{}
for _, row := range req.Detail.Upserts {
if row.NOnMLDetNo <= 0 {
skippedUpserts += 1
@@ -1537,10 +1551,74 @@ func PostProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http.
return
}
}
// Guard: keep part/section binding stable.
// UI sometimes doesn't send n_urt_mt_bolum_id; if we write 0 into spUrtOnMLMasDet,
// joins to spUrtMTBolum will break and "parca adi" will render as "-".
if row.NUrtMTBolumID <= 0 && row.NHammaddeTuruNo > 0 {
if cached, ok := mtBolumByHammadde[row.NHammaddeTuruNo]; ok {
if cached > 0 {
row.NUrtMTBolumID = cached
}
} else {
var mtID int
err := tx.QueryRowContext(ctx, `
SELECT TOP 1 ISNULL(MTnUrtMTBolumID, 0) AS MTnUrtMTBolumID
FROM dbo.spUrtOnMLHammaddeTuru WITH (NOLOCK)
WHERE nHammaddeTuruNo = @p1
`, row.NHammaddeTuruNo).Scan(&mtID)
if err != nil && err != sql.ErrNoRows {
logger.Warn("mt bolum lookup error (will keep incoming value)",
"trace_id", traceID,
"n_onml_no", nOnMLNo,
"n_onml_det_no", row.NOnMLDetNo,
"n_hammadde_turu_no", row.NHammaddeTuruNo,
"err", err,
)
mtBolumByHammadde[row.NHammaddeTuruNo] = 0
} else {
mtBolumByHammadde[row.NHammaddeTuruNo] = mtID
if mtID > 0 {
row.NUrtMTBolumID = mtID
logger.Info("mt bolum auto-filled from hammadde master",
"trace_id", traceID,
"n_onml_no", nOnMLNo,
"n_onml_det_no", row.NOnMLDetNo,
"n_hammadde_turu_no", row.NHammaddeTuruNo,
"n_urt_mt_bolum_id", mtID,
)
} else {
logger.Warn("mt bolum missing on hammadde master (keeping 0)",
"trace_id", traceID,
"n_onml_no", nOnMLNo,
"n_onml_det_no", row.NOnMLDetNo,
"n_hammadde_turu_no", row.NHammaddeTuruNo,
)
}
}
}
}
qty := row.LMiktar
if qty < 0 {
qty = 0
}
// Build recipe sync source data:
// - never include CM2 / labor groups
// - never include empty codes
// - use variantless code (we already normalize sKodu on read; here we trust request)
if req.Header.NUrtReceteID > 0 {
group := strings.ToUpper(strings.TrimSpace(row.SAciklama3))
code := strings.TrimSpace(row.SKodu)
if code != "" && group != "CM2" && !strings.Contains(strings.ToUpper(code), " CM2") {
if row.NHammaddeTuruNo > 0 {
k := recipeKey{nUrtMBolumID: row.NHammaddeTuruNo, sKodu: code}
recipeQtyByKey[k] += qty
}
}
}
cur := strings.ToUpper(strings.TrimSpace(row.FiyatDoviz))
in := row.FiyatGirilen
unitTRY := in
@@ -1745,8 +1823,150 @@ WHEN NOT MATCHED THEN
logger.Warn("detail upserts skipped summary (det_no<=0)", "trace_id", traceID, "n_onml_no", nOnMLNo, "skipped", skippedUpserts)
}
// NOTE: Recipe tables are intentionally NOT synced from OnML saves.
// This costing screen is the source of truth only for dbo.spUrtOnMLMas / dbo.spUrtOnMLMasDet.
// Recipe sync (spUrtRecMBolum): insert missing rows, update qty when changed.
// IMPORTANT: We sync only variantless item codes (sModel-like) from OnML and never write CM2 items.
if req.Header.NUrtReceteID > 0 && len(recipeQtyByKey) > 0 {
logger.Info("tx step", "trace_id", traceID, "n_onml_no", nOnMLNo, "step", "recipe_sync", "n_urt_recete_id", req.Header.NUrtReceteID, "src_count", len(recipeQtyByKey))
// Determine default nUrtUBolumID from existing recipe rows; fallback to 13 (matches current data).
nUrtUBolumID := 13
_ = tx.QueryRowContext(ctx, `
SELECT TOP 1 ISNULL(CONVERT(int, nUrtUBolumID), 0) AS nUrtUBolumID
FROM dbo.spUrtRecMBolum WITH (NOLOCK)
WHERE nUrtReceteID = @p1
ORDER BY nUrtRecMBolumID ASC
`, req.Header.NUrtReceteID).Scan(&nUrtUBolumID)
if nUrtUBolumID <= 0 {
nUrtUBolumID = 13
}
// Load existing rows for quick compare.
existingQty := map[recipeKey]float64{}
if rows, err := tx.QueryContext(ctx, `
SELECT
ISNULL(CONVERT(int, nUrtMBolumID), 0) AS nUrtMBolumID,
LTRIM(RTRIM(ISNULL(nHStokID_G,''))) AS nHStokID_G,
ISNULL(CONVERT(float, lHMiktar_G), 0) AS lHMiktar_G
FROM dbo.spUrtRecMBolum WITH (NOLOCK)
WHERE nUrtReceteID = @p1
`, req.Header.NUrtReceteID); err == nil {
for rows.Next() {
var bolumID int
var code string
var q float64
if err := rows.Scan(&bolumID, &code, &q); err != nil {
continue
}
code = strings.TrimSpace(code)
if bolumID > 0 && code != "" {
existingQty[recipeKey{nUrtMBolumID: bolumID, sKodu: code}] = q
}
}
_ = rows.Close()
}
// Update changed quantities.
updated := 0
for k, q := range recipeQtyByKey {
old, ok := existingQty[k]
if !ok {
continue
}
if old == q {
continue
}
if _, err := tx.ExecContext(ctx, `
UPDATE dbo.spUrtRecMBolum
SET
lHMiktar_G = @p4,
sKullaniciAdiDeg = @p5,
dteIslemTarihiDeg = GETDATE()
WHERE nUrtReceteID = @p1
AND nUrtMBolumID = @p2
AND LTRIM(RTRIM(ISNULL(nHStokID_G,''))) = @p3
`, req.Header.NUrtReceteID, k.nUrtMBolumID, k.sKodu, q, user); err != nil {
logger.Warn("recipe qty update failed", "trace_id", traceID, "n_urt_recete_id", req.Header.NUrtReceteID, "n_urt_m_bolum_id", k.nUrtMBolumID, "s_kodu", k.sKodu, "err", err)
continue
}
updated++
}
// Insert missing rows.
// We must generate nUrtRecMBolumID (smallint, non-identity) manually.
var baseID int
if err := tx.QueryRowContext(ctx, `
SELECT ISNULL(MAX(CONVERT(int, nUrtRecMBolumID)), 0) AS MaxID
FROM dbo.spUrtRecMBolum WITH (UPDLOCK, HOLDLOCK)
`).Scan(&baseID); err != nil {
logger.Warn("recipe base id lookup failed (skipping inserts)", "trace_id", traceID, "err", err)
} else {
inserted := 0
nextID := baseID
for k, q := range recipeQtyByKey {
if _, ok := existingQty[k]; ok {
continue
}
// FK guard: only insert if nUrtMBolumID exists in spUrtMBolum.
var bolumExists int
if err := tx.QueryRowContext(ctx, `
SELECT CASE WHEN EXISTS (SELECT 1 FROM dbo.spUrtMBolum WITH (NOLOCK) WHERE nUrtMBolumID = @p1) THEN 1 ELSE 0 END
`, k.nUrtMBolumID).Scan(&bolumExists); err != nil || bolumExists != 1 {
logger.Warn("recipe insert skipped (missing spUrtMBolum FK)", "trace_id", traceID, "n_urt_m_bolum_id", k.nUrtMBolumID, "s_kodu", k.sKodu)
continue
}
nextID++
if nextID > 32767 {
logger.Warn("recipe insert stopped (nUrtRecMBolumID overflow)", "trace_id", traceID, "base_id", baseID, "next_id", nextID)
break
}
// NOTE: sIslemKodu is NOT NULL; keep empty string as default.
// Keep lMiktar_G at 0 (NOT NULL) to avoid producing NULL rows.
if _, err := tx.ExecContext(ctx, `
INSERT INTO dbo.spUrtRecMBolum (
nUrtRecMBolumID,
nUrtReceteID,
nUrtUBolumID,
nUrtMBolumID,
nUrtMTBolumID,
nStokTipiID,
nHStokID_G,
lHMiktar_G,
lHFire_G,
lHCarpan,
nMaliyetTipiID,
lHMaliyet_G,
nMTalimat_G,
bIslem,
lMiktar_G,
nSure,
sIslemKodu,
lHMiktar_GHedef,
nMBolumSarfTipiNo,
sKullaniciAdi,
dteIslemTarihi
)
VALUES (
@p1,@p2,@p3,@p4,
0,1,@p5,
@p6,0,1,
6,0,2,
0,0,0,
'',
0,1,
@p7,GETDATE()
)
`, nextID, req.Header.NUrtReceteID, nUrtUBolumID, k.nUrtMBolumID, k.sKodu, q, user); err != nil {
logger.Warn("recipe insert failed", "trace_id", traceID, "n_urt_recete_id", req.Header.NUrtReceteID, "n_urt_m_bolum_id", k.nUrtMBolumID, "s_kodu", k.sKodu, "err", err)
continue
}
inserted++
}
logger.Info("recipe sync done", "trace_id", traceID, "n_onml_no", nOnMLNo, "n_urt_recete_id", req.Header.NUrtReceteID, "updated", updated, "inserted", inserted)
}
}
logger.Info("tx step", "trace_id", traceID, "n_onml_no", nOnMLNo, "step", "commit")
if err := tx.Commit(); err != nil {