Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -1228,6 +1228,15 @@ func PostProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http.
|
|||||||
http.Error(w, "Gecersiz JSON", http.StatusBadRequest)
|
http.Error(w, "Gecersiz JSON", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Guardrail: do not allow blank stock codes for rows that specify a hammadde type.
|
||||||
|
// UI should already block this, but we enforce it server-side too.
|
||||||
|
for _, row := range req.Detail.Upserts {
|
||||||
|
if row.NHammaddeTuruNo > 0 && strings.TrimSpace(row.SKodu) == "" {
|
||||||
|
logger.Warn("validation failed: blank s_kodu", "n_hammadde_turu_no", row.NHammaddeTuruNo, "n_onml_det_no", row.NOnMLDetNo)
|
||||||
|
http.Error(w, "Kod bos olamaz (hammadde turu secili satir var)", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
req.DetailSource = strings.ToLower(strings.TrimSpace(req.DetailSource))
|
req.DetailSource = strings.ToLower(strings.TrimSpace(req.DetailSource))
|
||||||
req.Header.UrunKodu = strings.TrimSpace(req.Header.UrunKodu)
|
req.Header.UrunKodu = strings.TrimSpace(req.Header.UrunKodu)
|
||||||
@@ -1696,19 +1705,23 @@ WHEN NOT MATCHED THEN
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
// Recipe sync (URETIM): ensure recipe contains all OnML hammadde rows
|
// Recipe sync (URETIM): ensure recipe contains all OnML hammadde rows
|
||||||
// so future no-cost loads don't keep showing them as missing.
|
// so future no-cost loads don't keep showing them as missing.
|
||||||
// Table observed in queries: dbo.spUrtRecMBolumMik (nUrtMBolumID stores nHammaddeTuruNo).
|
// IMPORTANT: In current URETIM DB, the detail table is dbo.spUrtRecMBolum (NOT NULL cols, FK to dbo.spUrtMBolum).
|
||||||
|
// We must:
|
||||||
|
// 1) Skip hammadde types that do not exist in dbo.spUrtMBolum (FK safety),
|
||||||
|
// 2) Upsert by (nUrtReceteID, nUrtMBolumID, nHStokID_G=sKodu),
|
||||||
|
// 3) When inserting, generate nUrtRecMBolumID (smallint, not identity) and fill required columns incl. sIslemKodu=''.
|
||||||
// ============================================================
|
// ============================================================
|
||||||
if req.Header.NUrtReceteID > 0 {
|
if req.Header.NUrtReceteID > 0 {
|
||||||
receteID := req.Header.NUrtReceteID
|
receteID := req.Header.NUrtReceteID
|
||||||
logger.Info("tx step", "trace_id", traceID, "n_onml_no", nOnMLNo, "step", "recipe_sync", "n_urt_recete_id", receteID)
|
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)
|
// Determine next available recipe detail id (nUrtRecMBolumID) globally.
|
||||||
|
// NOTE: nUrtRecMBolumID is smallint and not identity in this schema.
|
||||||
nextRecDetID := 0
|
nextRecDetID := 0
|
||||||
_ = tx.QueryRowContext(ctx, `
|
_ = tx.QueryRowContext(ctx, `
|
||||||
SELECT ISNULL(MAX(RMik.nUrtRecMBolumID), 0) + 1
|
SELECT ISNULL(MAX(R.nUrtRecMBolumID), 0) + 1
|
||||||
FROM dbo.spUrtRecMBolumMik RMik WITH (UPDLOCK, HOLDLOCK)
|
FROM dbo.spUrtRecMBolum R WITH (UPDLOCK, HOLDLOCK)
|
||||||
WHERE RMik.nUrtReceteID = @p1
|
`).Scan(&nextRecDetID)
|
||||||
`, receteID).Scan(&nextRecDetID)
|
|
||||||
if nextRecDetID <= 0 {
|
if nextRecDetID <= 0 {
|
||||||
nextRecDetID = 1
|
nextRecDetID = 1
|
||||||
}
|
}
|
||||||
@@ -1718,6 +1731,11 @@ WHERE RMik.nUrtReceteID = @p1
|
|||||||
if hNo <= 0 {
|
if hNo <= 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Legacy mapping: merge deprecated hammadde types into canonical ones.
|
||||||
|
// We migrated 1104 -> 1105 historically; keep runtime mapping to avoid FK issues.
|
||||||
|
if hNo == 1104 {
|
||||||
|
hNo = 1105
|
||||||
|
}
|
||||||
|
|
||||||
// 1. FILTER: CM1/CM2 (Labor/Service) rows must NOT be written back into recipe tables.
|
// 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.
|
// We check the group label (sAciklama3) from the row itself.
|
||||||
@@ -1727,132 +1745,93 @@ WHERE RMik.nUrtReceteID = @p1
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// In this version of URETIM DB, the table name is dbo.spUrtRecMBolumMik
|
// FK safety: nUrtMBolumID must exist in dbo.spUrtMBolum.
|
||||||
// but it uses _G suffixes for quantity and amount columns.
|
var bolumExists int
|
||||||
// Also nHStokID_G stores the stock code string rather than just an ID.
|
|
||||||
rawSKodu := strings.TrimSpace(row.SKodu)
|
|
||||||
|
|
||||||
// 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(§ionExists)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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, `
|
if err := tx.QueryRowContext(ctx, `
|
||||||
SELECT COUNT(1)
|
SELECT COUNT(1) FROM dbo.spUrtMBolum WITH (NOLOCK)
|
||||||
FROM dbo.spUrtRecMBolumMik RMik WITH (NOLOCK)
|
WHERE nUrtMBolumID = @p1
|
||||||
WHERE RMik.nUrtReceteID = @p1
|
`, hNo).Scan(&bolumExists); err != nil || bolumExists <= 0 {
|
||||||
AND RMik.nUrtMBolumID = @p2
|
logger.Warn("recipe sync skip: missing spUrtMBolum", "n_urt_m_bolum_id", hNo, "s_kodu", strings.TrimSpace(row.SKodu))
|
||||||
AND LTRIM(RTRIM(RMik.nHStokID_G)) = @p3
|
|
||||||
`, receteID, hNo, rawSKodu).Scan(&exists); err == nil && exists > 0 {
|
|
||||||
// Compute TRY unit price for recipe mirror columns.
|
|
||||||
cur := strings.ToUpper(strings.TrimSpace(row.FiyatDoviz))
|
|
||||||
in := row.FiyatGirilen
|
|
||||||
unitTRYRec := in
|
|
||||||
switch cur {
|
|
||||||
case "USD":
|
|
||||||
unitTRYRec = in * usdRate
|
|
||||||
case "EUR":
|
|
||||||
unitTRYRec = in * eurRate
|
|
||||||
case "GBP":
|
|
||||||
unitTRYRec = in * gbpRate
|
|
||||||
case "TRY", "TL", "":
|
|
||||||
unitTRYRec = in
|
|
||||||
default:
|
|
||||||
unitTRYRec = in
|
|
||||||
}
|
|
||||||
_, _ = tx.ExecContext(ctx, `
|
|
||||||
UPDATE dbo.spUrtRecMBolumMik
|
|
||||||
SET lHMiktar_G = @p4,
|
|
||||||
lHMaliyet_G = @p5,
|
|
||||||
sKullaniciAdiDeg = @p6,
|
|
||||||
dteIslemTarihiDeg = GETDATE(),
|
|
||||||
bIslem = @p7
|
|
||||||
WHERE nUrtReceteID = @p1
|
|
||||||
AND nUrtMBolumID = @p2
|
|
||||||
AND LTRIM(RTRIM(nHStokID_G)) = @p3
|
|
||||||
`, receteID, hNo, rawSKodu, row.LMiktar, unitTRYRec, user, row.MaliyeteDahil)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert missing: using _G columns and storing code in nHStokID_G.
|
// Upsert target key: (receteID, hNo, sKodu).
|
||||||
// Compute TRY unit price for recipe mirror columns.
|
rawSKodu := strings.TrimSpace(row.SKodu)
|
||||||
cur := strings.ToUpper(strings.TrimSpace(row.FiyatDoviz))
|
if rawSKodu == "" {
|
||||||
in := row.FiyatGirilen
|
continue
|
||||||
unitTRYRec := in
|
}
|
||||||
switch cur {
|
|
||||||
case "USD":
|
// Update qty if exists.
|
||||||
unitTRYRec = in * usdRate
|
var exists int
|
||||||
case "EUR":
|
if err := tx.QueryRowContext(ctx, `
|
||||||
unitTRYRec = in * eurRate
|
SELECT COUNT(1)
|
||||||
case "GBP":
|
FROM dbo.spUrtRecMBolum R WITH (NOLOCK)
|
||||||
unitTRYRec = in * gbpRate
|
WHERE R.nUrtReceteID = @p1
|
||||||
case "TRY", "TL", "":
|
AND R.nUrtMBolumID = @p2
|
||||||
unitTRYRec = in
|
AND LTRIM(RTRIM(R.nHStokID_G)) = @p3
|
||||||
default:
|
`, receteID, hNo, rawSKodu).Scan(&exists); err == nil && exists > 0 {
|
||||||
unitTRYRec = in
|
_, _ = tx.ExecContext(ctx, `
|
||||||
|
UPDATE dbo.spUrtRecMBolum
|
||||||
|
SET lHMiktar_G = @p4
|
||||||
|
WHERE nUrtReceteID = @p1
|
||||||
|
AND nUrtMBolumID = @p2
|
||||||
|
AND LTRIM(RTRIM(nHStokID_G)) = @p3
|
||||||
|
`, receteID, hNo, rawSKodu, row.LMiktar)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert missing into dbo.spUrtRecMBolum.
|
||||||
|
// nUrtRecMBolumID is not identity; keep incrementing, but guard against smallint overflow.
|
||||||
|
if nextRecDetID > 32767 {
|
||||||
|
logger.Warn("recipe sync skip: nUrtRecMBolumID overflow risk", "next_id", nextRecDetID, "n_urt_recete_id", receteID)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
_, insertErr := tx.ExecContext(ctx, `
|
_, insertErr := tx.ExecContext(ctx, `
|
||||||
INSERT INTO dbo.spUrtRecMBolumMik (
|
INSERT INTO dbo.spUrtRecMBolum (
|
||||||
|
nUrtRecMBolumID,
|
||||||
nUrtReceteID,
|
nUrtReceteID,
|
||||||
nUrtUBolumID,
|
nUrtUBolumID,
|
||||||
nUrtRecMBolumID,
|
nUrtMBolumID,
|
||||||
nStokID,
|
nUrtMTBolumID,
|
||||||
|
nStokTipiID,
|
||||||
nHStokID_G,
|
nHStokID_G,
|
||||||
lHMiktar_G,
|
lHMiktar_G,
|
||||||
lHFire_G,
|
lHFire_G,
|
||||||
|
lHCarpan,
|
||||||
nMaliyetTipiID,
|
nMaliyetTipiID,
|
||||||
lHMaliyet_G,
|
lHMaliyet_G,
|
||||||
lMiktar_G,
|
nMTalimat_G,
|
||||||
sIslemKodu,
|
|
||||||
nUrtMBolumID,
|
|
||||||
nUrtMTBolumID,
|
|
||||||
lHCarpan,
|
|
||||||
bIslem,
|
bIslem,
|
||||||
nSure,
|
nSure,
|
||||||
sAciklama,
|
sIslemKodu,
|
||||||
sKullaniciAdi,
|
lHMiktar_GHedef,
|
||||||
dteIslemTarihi,
|
|
||||||
nMBolumSarfTipiNo
|
nMBolumSarfTipiNo
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
@p1,
|
@p1, -- nUrtRecMBolumID (smallint)
|
||||||
13,
|
@p2, -- nUrtReceteID
|
||||||
@p2,
|
@p3, -- nUrtUBolumID
|
||||||
0,
|
@p4, -- nUrtMBolumID
|
||||||
@p3, -- nHStokID_G (Code)
|
0, -- nUrtMTBolumID (tinyint)
|
||||||
@p4, -- lHMiktar_G
|
1, -- nStokTipiID
|
||||||
0,
|
@p5, -- nHStokID_G (sKodu)
|
||||||
6,
|
@p6, -- lHMiktar_G
|
||||||
@p5, -- lHMaliyet_G
|
0, -- lHFire_G
|
||||||
1, -- lMiktar_G
|
1, -- lHCarpan
|
||||||
'',
|
6, -- nMaliyetTipiID
|
||||||
@p6,
|
0, -- lHMaliyet_G
|
||||||
@p7,
|
2, -- nMTalimat_G
|
||||||
1,
|
0, -- bIslem
|
||||||
@p8, -- bIslem
|
0, -- nSure
|
||||||
0,
|
'', -- sIslemKodu (NOT NULL)
|
||||||
NULL,
|
0, -- lHMiktar_GHedef
|
||||||
@p9,
|
1 -- nMBolumSarfTipiNo
|
||||||
GETDATE(),
|
|
||||||
1
|
|
||||||
)
|
)
|
||||||
`, receteID, nextRecDetID, rawSKodu, row.LMiktar, unitTRYRec, hNo, row.NUrtMTBolumID, row.MaliyeteDahil, user)
|
`, nextRecDetID, receteID, 13, hNo, rawSKodu, row.LMiktar)
|
||||||
if insertErr == nil {
|
if insertErr == nil {
|
||||||
nextRecDetID += 1
|
nextRecDetID += 1
|
||||||
|
} else {
|
||||||
|
logger.Warn("recipe sync insert error", "err", insertErr, "n_urt_recete_id", receteID, "n_urt_m_bolum_id", hNo, "s_kodu", rawSKodu)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3627,6 +3627,29 @@ async function saveChanges () {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate: "Kod" (sKodu) is mandatory for any row that has a hammadde type.
|
||||||
|
// We block saving if there are rows with empty/whitespace code, to avoid sending blank rows to backend.
|
||||||
|
const blankCodeRows = (Array.isArray(flatDetailRows.value) ? flatDetailRows.value : [])
|
||||||
|
.filter(r => {
|
||||||
|
const hNo = parseInt(String(r?.nHammaddeTuruNo || '').trim() || '0', 10) || 0
|
||||||
|
if (!(hNo > 0)) return false
|
||||||
|
return String(r?.sKodu || '').trim() === ''
|
||||||
|
})
|
||||||
|
if (blankCodeRows.length > 0) {
|
||||||
|
const next = {}
|
||||||
|
blankCodeRows.forEach(r => {
|
||||||
|
const key = String(r?.__rowKey || '').trim()
|
||||||
|
if (key) next[key] = true
|
||||||
|
})
|
||||||
|
requiredAttentionRowKeys.value = next
|
||||||
|
$q.notify({
|
||||||
|
type: 'warning',
|
||||||
|
message: `Kod bos birakilan satirlar var. Kaydetmeden once Kod alanini doldurun. (Adet: ${blankCodeRows.length})`,
|
||||||
|
position: 'top-right'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const header = detailHeader.value
|
const header = detailHeader.value
|
||||||
const upserts = flatDetailRows.value.map(r => ({
|
const upserts = flatDetailRows.value.map(r => ({
|
||||||
n_onml_det_no: parseInt(String(r?.nOnMLDetNo || '').trim() || '0', 10) || 0,
|
n_onml_det_no: parseInt(String(r?.nOnMLDetNo || '').trim() || '0', 10) || 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user