Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -21,7 +21,8 @@ UI_DIR=/opt/bssapp/ui/dist
|
||||
# ===============================
|
||||
# DATABASES
|
||||
# ===============================
|
||||
POSTGRES_CONN=host=46.224.33.150 port=5432 user=postgres password=tayitkan dbname=baggib2b sslmode=disable
|
||||
# Local dev: connect via SSH tunnel to the server (Windows -> 15432 -> server 127.0.0.1:5432).
|
||||
POSTGRES_CONN=host=127.0.0.1 port=15432 user=postgres password=tayitkan dbname=baggib2b sslmode=disable
|
||||
MSSQL_CONN=sqlserver://sa:Gil_0150@10.0.0.9:1433?database=BAGGI_V3&encrypt=disable
|
||||
URETIM_MSSQL_CONN=sqlserver://sa:Gil_0150@10.0.0.9:1433?database=URETIM&encrypt=disable
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
@@ -906,6 +907,12 @@ func main() {
|
||||
log.Println("⚠️ .env / mail.env bulunamadı")
|
||||
}
|
||||
|
||||
// Local dev convenience: on Windows we generally want to override .env with .env.local
|
||||
// (e.g. SSH-tunnel PostgreSQL on 127.0.0.1:15432).
|
||||
if runtime.GOOS == "windows" {
|
||||
_ = godotenv.Overload(".env.local")
|
||||
}
|
||||
|
||||
jwtSecret := os.Getenv("JWT_SECRET")
|
||||
if len(jwtSecret) < 10 {
|
||||
log.Fatal("❌ JWT_SECRET tanımlı değil veya çok kısa (min 10 karakter)")
|
||||
|
||||
@@ -98,6 +98,7 @@ type ProductionHasCostDetailHeader struct {
|
||||
NOnMLNo string `json:"nOnMLNo"`
|
||||
UrunKodu string `json:"UrunKodu"`
|
||||
UrunAdi string `json:"UrunAdi"`
|
||||
UrunIlkGrubu string `json:"UrunIlkGrubu"`
|
||||
UrunAnaGrubu string `json:"UrunAnaGrubu"`
|
||||
UrunAltGrubu string `json:"UrunAltGrubu"`
|
||||
UretimSekliID string `json:"UretimSekliID"`
|
||||
@@ -131,6 +132,7 @@ type ProductionHasCostDetailEditorOption struct {
|
||||
NHammaddeTuruNo string `json:"nHammaddeTuruNo"`
|
||||
SHammaddeTuruAdi string `json:"sHammaddeTuruAdi"`
|
||||
SAciklama3 string `json:"sAciklama3"`
|
||||
MTUrtMTBolumID int `json:"mtUrtMTBolumID"`
|
||||
SKodu string `json:"sKodu"`
|
||||
SAciklama string `json:"sAciklama"`
|
||||
SModel string `json:"sModel"`
|
||||
|
||||
@@ -37,6 +37,34 @@ ORDER BY ProductCode;
|
||||
return urunAnaGrubu, urunAltGrubu, nil
|
||||
}
|
||||
|
||||
func GetProductIlkAnaAltGrupByUrunKodu(ctx context.Context, mssqlDB *sql.DB, urunKodu string) (urunIlkGrubu string, urunAnaGrubu string, urunAltGrubu string, err error) {
|
||||
urunKodu = strings.TrimSpace(urunKodu)
|
||||
if mssqlDB == nil || urunKodu == "" {
|
||||
return "", "", "", nil
|
||||
}
|
||||
|
||||
// Nebim V3: ProductFilterWithDescription exposes ProductAtt42Desc (ilk grup), ProductAtt01Desc (ana), ProductAtt02Desc (alt).
|
||||
sqlText := `
|
||||
SELECT TOP 1
|
||||
ISNULL(ProductAtt42Desc, '') AS UrunIlkGrubu,
|
||||
ISNULL(ProductAtt01Desc, '') AS UrunAnaGrubu,
|
||||
ISNULL(ProductAtt02Desc, '') AS UrunAltGrubu
|
||||
FROM ProductFilterWithDescription('TR')
|
||||
WHERE IsBlocked = 0
|
||||
AND LTRIM(RTRIM(ProductCode)) = @p1
|
||||
ORDER BY ProductCode;
|
||||
`
|
||||
|
||||
row := mssqlDB.QueryRowContext(ctx, sqlText, urunKodu)
|
||||
if err := row.Scan(&urunIlkGrubu, &urunAnaGrubu, &urunAltGrubu); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return "", "", "", nil
|
||||
}
|
||||
return "", "", "", err
|
||||
}
|
||||
return urunIlkGrubu, urunAnaGrubu, urunAltGrubu, nil
|
||||
}
|
||||
|
||||
func GetProductionProductCostingAnaGrupOptions(ctx context.Context, mssqlDB *sql.DB, search string, limit int) (*sql.Rows, error) {
|
||||
search = strings.TrimSpace(search)
|
||||
if limit <= 0 {
|
||||
@@ -131,6 +159,7 @@ SELECT TOP (@p2)
|
||||
ISNULL(B.sAdi, '') AS sAdi
|
||||
FROM dbo.spUrtMTBolum B WITH (NOLOCK)
|
||||
WHERE ISNULL(B.bAktif, 0) = 1
|
||||
AND ISNULL(B.nUrtTipiID, 0) = 1
|
||||
AND (@p1 = '' OR ISNULL(B.sAdi, '') LIKE @p3 OR CONVERT(VARCHAR(32), ISNULL(B.nUrtMTBolumID, 0)) LIKE @p3)
|
||||
ORDER BY B.nUrtMTBolumID
|
||||
`
|
||||
@@ -160,6 +189,7 @@ SELECT
|
||||
FROM dbo.mk_MaliyetParcaEslestirme M WITH (NOLOCK)
|
||||
LEFT JOIN dbo.spUrtMTBolum B WITH (NOLOCK)
|
||||
ON B.nUrtMTBolumID = M.nUrtMTBolumID
|
||||
AND ISNULL(B.nUrtTipiID, 0) = 1
|
||||
OUTER APPLY (
|
||||
SELECT
|
||||
STUFF((
|
||||
@@ -268,6 +298,97 @@ DECLARE @id INT;
|
||||
return mappingID, nil
|
||||
}
|
||||
|
||||
type ProductionProductCostingInvalidHammaddeForPart struct {
|
||||
NHammaddeTuruNo int
|
||||
Aciklama string
|
||||
MTBolumID int
|
||||
}
|
||||
|
||||
// FilterHammaddeTurleriForPart enforces spUrtOnMLHammaddeTuru.MTnUrtMTBolumID rules:
|
||||
// - If MTnUrtMTBolumID > 0, the hammadde type is allowed only for that part (nUrtMTBolumID).
|
||||
// - If MTnUrtMTBolumID is 0/NULL, it is considered global/unknown and allowed.
|
||||
func FilterHammaddeTurleriForPart(
|
||||
ctx context.Context,
|
||||
uretimDB *sql.DB,
|
||||
nUrtMTBolumID int,
|
||||
nHammaddeTurleri []int,
|
||||
) (allowed []int, invalid []ProductionProductCostingInvalidHammaddeForPart, err error) {
|
||||
seen := make(map[int]bool, len(nHammaddeTurleri))
|
||||
unique := make([]int, 0, len(nHammaddeTurleri))
|
||||
for _, n := range nHammaddeTurleri {
|
||||
if n <= 0 || seen[n] {
|
||||
continue
|
||||
}
|
||||
seen[n] = true
|
||||
unique = append(unique, n)
|
||||
}
|
||||
if len(unique) == 0 {
|
||||
return []int{}, []ProductionProductCostingInvalidHammaddeForPart{}, nil
|
||||
}
|
||||
|
||||
// Build IN list safely.
|
||||
ph := make([]string, 0, len(unique))
|
||||
args := make([]any, 0, len(unique))
|
||||
for i, n := range unique {
|
||||
ph = append(ph, fmt.Sprintf("@p%d", i+1))
|
||||
args = append(args, n)
|
||||
}
|
||||
|
||||
sqlText := fmt.Sprintf(`
|
||||
SELECT
|
||||
ISNULL(H.nHammaddeTuruNo, 0) AS nHammaddeTuruNo,
|
||||
ISNULL(H.sAciklama, '') AS sAciklama,
|
||||
ISNULL(H.MTnUrtMTBolumID, 0) AS MTnUrtMTBolumID
|
||||
FROM dbo.spUrtOnMLHammaddeTuru H WITH (NOLOCK)
|
||||
WHERE H.nHammaddeTuruNo IN (%s)
|
||||
`, strings.Join(ph, ","))
|
||||
|
||||
rows, qerr := uretimDB.QueryContext(ctx, sqlText, args...)
|
||||
if qerr != nil {
|
||||
return nil, nil, qerr
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
type meta struct {
|
||||
aciklama string
|
||||
mtBolumID int
|
||||
}
|
||||
metaByNo := map[int]meta{}
|
||||
for rows.Next() {
|
||||
var no int
|
||||
var aciklama string
|
||||
var mtBolum int
|
||||
if scanErr := rows.Scan(&no, &aciklama, &mtBolum); scanErr != nil {
|
||||
return nil, nil, scanErr
|
||||
}
|
||||
metaByNo[no] = meta{aciklama: strings.TrimSpace(aciklama), mtBolumID: mtBolum}
|
||||
}
|
||||
if rowsErr := rows.Err(); rowsErr != nil {
|
||||
return nil, nil, rowsErr
|
||||
}
|
||||
|
||||
allowed = make([]int, 0, len(unique))
|
||||
invalid = make([]ProductionProductCostingInvalidHammaddeForPart, 0)
|
||||
for _, n := range unique {
|
||||
m, ok := metaByNo[n]
|
||||
if !ok {
|
||||
// Unknown in lookup; allow (we can't validate).
|
||||
allowed = append(allowed, n)
|
||||
continue
|
||||
}
|
||||
if m.mtBolumID > 0 && nUrtMTBolumID > 0 && m.mtBolumID != nUrtMTBolumID {
|
||||
invalid = append(invalid, ProductionProductCostingInvalidHammaddeForPart{
|
||||
NHammaddeTuruNo: n,
|
||||
Aciklama: m.aciklama,
|
||||
MTBolumID: m.mtBolumID,
|
||||
})
|
||||
continue
|
||||
}
|
||||
allowed = append(allowed, n)
|
||||
}
|
||||
return allowed, invalid, nil
|
||||
}
|
||||
|
||||
func SetProductionProductCostingParcaMappingActive(ctx context.Context, uretimDB *sql.DB, id int, bAktif bool, user string) error {
|
||||
user = strings.TrimSpace(user)
|
||||
activeVal := 0
|
||||
@@ -699,7 +820,8 @@ WITH RecipeMatch AS (
|
||||
),
|
||||
HammaddeTekil AS (
|
||||
SELECT
|
||||
ISNULL(NULLIF(LTRIM(RTRIM(HT.sAciklama3)), ''), ISNULL(NULLIF(LTRIM(RTRIM(HT.sAciklama)), ''), N'TANIMSIZ')) AS sAciklama3,
|
||||
-- Group label: DT/TP/CM2/FABRIC... Prefer sAciklama3, then sAciklama2. Never fall back to sAciklama (name).
|
||||
COALESCE(NULLIF(LTRIM(RTRIM(HT.sAciklama3)), ''), NULLIF(LTRIM(RTRIM(HT.sAciklama2)), ''), N'TANIMSIZ') AS sAciklama3,
|
||||
ISNULL(HT.nHammaddeTuruNo, 0) AS nHammaddeTuruNoSort,
|
||||
RTRIM(CONVERT(VARCHAR(32), ISNULL(HT.nHammaddeTuruNo, 0))) AS nHammaddeTuruNo,
|
||||
-- Match URETIM's sp_pUrtOnMaliyetRecetedenKop behavior: use model code + color code instead of variant stock code.
|
||||
@@ -709,6 +831,8 @@ HammaddeTekil AS (
|
||||
ISNULL(HT.sAciklama, '') AS sHammaddeTuruAdi,
|
||||
ISNULL(S.sBirimCinsi1, '') AS sBirim,
|
||||
ISNULL(RMik.lHMiktar, 0) AS lMiktar,
|
||||
ISNULL(HT.MTnUrtMTBolumID, 0) AS MTnUrtMTBolumID,
|
||||
ISNULL(B.sAdi, '') AS sParcaAdi,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY HT.nHammaddeTuruNo
|
||||
ORDER BY ISNULL(S.sModel, ISNULL(S.sKodu, ''))
|
||||
@@ -725,13 +849,20 @@ HammaddeTekil AS (
|
||||
SELECT TOP 1
|
||||
H.nHammaddeTuruNo,
|
||||
H.sAciklama,
|
||||
H.sAciklama3
|
||||
H.sAciklama2,
|
||||
H.sAciklama3,
|
||||
H.MTnUrtMTBolumID
|
||||
FROM dbo.spUrtOnMLHammaddeTuru H
|
||||
WHERE H.nUrtMBolumID = RMik.nUrtMBolumID
|
||||
-- In our data, RMik.nUrtMBolumID carries the required hammadde type number (e.g. 900/901/3300),
|
||||
-- not a "bolum id". So match by hammadde type number.
|
||||
WHERE H.nHammaddeTuruNo = RMik.nUrtMBolumID
|
||||
ORDER BY
|
||||
CASE WHEN H.nUrtMTBolumID = RMik.nUrtMTBolumID THEN 0 ELSE 1 END,
|
||||
CASE WHEN H.MTnUrtMTBolumID = RMik.nUrtMTBolumID THEN 0 ELSE 1 END,
|
||||
H.nHammaddeTuruNo
|
||||
) HT
|
||||
LEFT JOIN dbo.spUrtMTBolum B WITH (NOLOCK)
|
||||
ON B.nUrtMTBolumID = HT.MTnUrtMTBolumID
|
||||
AND ISNULL(B.nUrtTipiID, 0) = 1
|
||||
WHERE HT.nHammaddeTuruNo IS NOT NULL
|
||||
)
|
||||
SELECT
|
||||
@@ -762,7 +893,7 @@ SELECT
|
||||
0.0 AS gbpTutar,
|
||||
HT.sBirim,
|
||||
HT.sHammaddeTuruAdi,
|
||||
HT.sHammaddeTuruAdi AS sParcaAdi
|
||||
HT.sParcaAdi AS sParcaAdi
|
||||
FROM HammaddeTekil HT
|
||||
WHERE HT.rn = 1
|
||||
ORDER BY
|
||||
@@ -800,15 +931,22 @@ func GetProductionHasCostDetailHammaddeTypeOptions(
|
||||
SELECT TOP (@p2)
|
||||
RTRIM(CONVERT(VARCHAR(32), ISNULL(T.nHammaddeTuruNo, 0))) AS nHammaddeTuruNo,
|
||||
ISNULL(T.sAciklama, '') AS sHammaddeTuruAdi,
|
||||
ISNULL(NULLIF(LTRIM(RTRIM(T.sAciklama3)), ''), N'TANIMSIZ') AS sAciklama3
|
||||
FROM dbo.spUrtOnMLHammaddeTuru T
|
||||
WHERE
|
||||
ISNULL(T.bAktif, 0) = 1
|
||||
AND (
|
||||
COALESCE(NULLIF(LTRIM(RTRIM(T.sAciklama3)), ''), NULLIF(LTRIM(RTRIM(T.sAciklama2)), ''), N'TANIMSIZ') AS sAciklama3,
|
||||
ISNULL(T.MTnUrtMTBolumID, 0) AS mtUrtMTBolumID,
|
||||
ISNULL(B.sAdi, '') AS sParcaAdi
|
||||
FROM dbo.spUrtOnMLHammaddeTuru T WITH (NOLOCK)
|
||||
LEFT JOIN dbo.spUrtMTBolum B WITH (NOLOCK)
|
||||
ON B.nUrtMTBolumID = T.MTnUrtMTBolumID
|
||||
AND ISNULL(B.nUrtTipiID, 0) = 1
|
||||
WHERE
|
||||
ISNULL(T.bAktif, 0) = 1
|
||||
AND (
|
||||
@p1 = ''
|
||||
OR RTRIM(CONVERT(VARCHAR(32), ISNULL(T.nHammaddeTuruNo, 0))) LIKE @p3
|
||||
OR ISNULL(T.sAciklama, '') LIKE @p3
|
||||
OR ISNULL(T.sAciklama2, '') LIKE @p3
|
||||
OR ISNULL(T.sAciklama3, '') LIKE @p3
|
||||
OR ISNULL(B.sAdi, '') LIKE @p3
|
||||
)
|
||||
ORDER BY
|
||||
CASE
|
||||
|
||||
@@ -596,10 +596,11 @@ func GetProductionHasCostDetailHeaderHandler(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
logger.Info("request done", "n_urt_recete_id", item.NUrtReceteID, "urun_kodu", item.UrunKodu)
|
||||
if mssqlDB != nil {
|
||||
ana, alt, err := queries.GetProductAnaAltGrupByUrunKodu(ctx, mssqlDB, item.UrunKodu)
|
||||
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
|
||||
}
|
||||
@@ -659,10 +660,11 @@ func GetProductionHasCostDetailHeaderHandler(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
logger.Info("request done", "n_onml_no", item.NOnMLNo, "urun_kodu", item.UrunKodu, "n_urt_recete_id", item.NUrtReceteID)
|
||||
if mssqlDB != nil {
|
||||
ana, alt, err := queries.GetProductAnaAltGrupByUrunKodu(ctx, mssqlDB, item.UrunKodu)
|
||||
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
|
||||
}
|
||||
@@ -761,7 +763,7 @@ func GetProductionHasCostDetailEditorOptionsHandler(w http.ResponseWriter, r *ht
|
||||
list := make([]models.ProductionHasCostDetailEditorOption, 0, limit)
|
||||
for rows.Next() {
|
||||
var item models.ProductionHasCostDetailEditorOption
|
||||
if err := rows.Scan(&item.NHammaddeTuruNo, &item.SHammaddeTuruAdi, &item.SAciklama3); err != nil {
|
||||
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
|
||||
@@ -769,7 +771,6 @@ func GetProductionHasCostDetailEditorOptionsHandler(w http.ResponseWriter, r *ht
|
||||
item.Kind = "hammadde"
|
||||
item.Value = item.NHammaddeTuruNo
|
||||
item.Label = strings.TrimSpace(item.NHammaddeTuruNo + " - " + item.SHammaddeTuruAdi)
|
||||
item.SParcaAdi = item.SAciklama3
|
||||
list = append(list, item)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
@@ -1958,7 +1959,18 @@ func PostProductionProductCostingParcaMappingUpsertHandler(w http.ResponseWriter
|
||||
return
|
||||
}
|
||||
|
||||
id, err := queries.UpsertProductionProductCostingParcaMapping(ctx, uretimDB, req.UrunIlkGrubu, req.UrunAnaGrubu, req.UrunAltGrubu, req.NUrtMTBolumID, req.NHammaddeTurleri, req.BAktif, user)
|
||||
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)
|
||||
@@ -1966,7 +1978,10 @@ func PostProductionProductCostingParcaMappingUpsertHandler(w http.ResponseWriter
|
||||
}
|
||||
|
||||
logger.Info("request done", "id", id, "user", user, "bAktif", req.BAktif)
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"ok": true, "id": id})
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user