Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -61,6 +61,7 @@ type ProductionHasCostDetailGroupItem struct {
|
|||||||
NOnMLNo string `json:"nOnMLNo"`
|
NOnMLNo string `json:"nOnMLNo"`
|
||||||
NOnMLDetNo string `json:"nOnMLDetNo"`
|
NOnMLDetNo string `json:"nOnMLDetNo"`
|
||||||
NHammaddeTuruNo string `json:"nHammaddeTuruNo"`
|
NHammaddeTuruNo string `json:"nHammaddeTuruNo"`
|
||||||
|
NUrtMTBolumID string `json:"nUrtMTBolumID"`
|
||||||
SKodu string `json:"sKodu"`
|
SKodu string `json:"sKodu"`
|
||||||
SAciklama string `json:"sAciklama"`
|
SAciklama string `json:"sAciklama"`
|
||||||
SRenk string `json:"sRenk"`
|
SRenk string `json:"sRenk"`
|
||||||
|
|||||||
@@ -922,12 +922,13 @@ func GetProductionHasCostDetailRowsByOnMLNo(
|
|||||||
SUM(ISNULL(D.lMiktar, 0) * ISNULL(D.lDovizFiyati, 0)) OVER (
|
SUM(ISNULL(D.lMiktar, 0) * ISNULL(D.lDovizFiyati, 0)) OVER (
|
||||||
PARTITION BY ISNULL(NULLIF(LTRIM(RTRIM(D.sAciklama3)), ''), ISNULL(NULLIF(LTRIM(RTRIM(T.sAciklama3)), ''), N'TANIMSIZ'))
|
PARTITION BY ISNULL(NULLIF(LTRIM(RTRIM(D.sAciklama3)), ''), ISNULL(NULLIF(LTRIM(RTRIM(T.sAciklama3)), ''), N'TANIMSIZ'))
|
||||||
) AS GroupTotalUSDTutar,
|
) AS GroupTotalUSDTutar,
|
||||||
RTRIM(CONVERT(VARCHAR(32), ISNULL(D.nOnMLNo, 0))) AS nOnMLNo,
|
RTRIM(CONVERT(VARCHAR(32), ISNULL(D.nOnMLNo, 0))) AS nOnMLNo,
|
||||||
RTRIM(CONVERT(VARCHAR(32), ISNULL(D.nOnMLDetNo, 0))) AS nOnMLDetNo,
|
RTRIM(CONVERT(VARCHAR(32), ISNULL(D.nOnMLDetNo, 0))) AS nOnMLDetNo,
|
||||||
RTRIM(CONVERT(VARCHAR(32), ISNULL(D.nHammaddeTuruNo, 0))) AS nHammaddeTuruNo,
|
RTRIM(CONVERT(VARCHAR(32), ISNULL(D.nHammaddeTuruNo, 0))) AS nHammaddeTuruNo,
|
||||||
-- Normalize code to variantless (tbStok.sModel) when D.sKodu is a variant-coded stock record.
|
RTRIM(CONVERT(VARCHAR(32), ISNULL(D.nUrtMTBolumID, 0))) AS nUrtMTBolumID,
|
||||||
ISNULL(NULLIF(LTRIM(RTRIM(SX.sModel)), ''), ISNULL(D.sKodu, '')) AS sKodu,
|
-- Normalize code to variantless (tbStok.sModel) when D.sKodu is a variant-coded stock record.
|
||||||
ISNULL(NULLIF(LTRIM(RTRIM(SX.sAciklama)), ''), ISNULL(D.sAciklama, '')) AS sAciklama,
|
ISNULL(NULLIF(LTRIM(RTRIM(SX.sModel)), ''), ISNULL(D.sKodu, '')) AS sKodu,
|
||||||
|
ISNULL(NULLIF(LTRIM(RTRIM(SX.sAciklama)), ''), ISNULL(D.sAciklama, '')) AS sAciklama,
|
||||||
ISNULL(D.sRenk, '') AS sRenk,
|
ISNULL(D.sRenk, '') AS sRenk,
|
||||||
ISNULL(D.sBeden, '') AS sBeden,
|
ISNULL(D.sBeden, '') AS sBeden,
|
||||||
ISNULL(D.sAciklama2, '') AS sAciklama2,
|
ISNULL(D.sAciklama2, '') AS sAciklama2,
|
||||||
@@ -1141,6 +1142,7 @@ HammaddeTekil AS (
|
|||||||
ISNULL(S.sBirimCinsi1, '') AS sBirim,
|
ISNULL(S.sBirimCinsi1, '') AS sBirim,
|
||||||
ISNULL(RMik.lHMiktar, 0) AS lMiktar,
|
ISNULL(RMik.lHMiktar, 0) AS lMiktar,
|
||||||
ISNULL(HT.MTnUrtMTBolumID, 0) AS MTnUrtMTBolumID,
|
ISNULL(HT.MTnUrtMTBolumID, 0) AS MTnUrtMTBolumID,
|
||||||
|
RTRIM(CONVERT(VARCHAR(32), ISNULL(HT.MTnUrtMTBolumID, 0))) AS nUrtMTBolumID,
|
||||||
ISNULL(B.sAdi, '') AS sParcaAdi,
|
ISNULL(B.sAdi, '') AS sParcaAdi,
|
||||||
ROW_NUMBER() OVER (
|
ROW_NUMBER() OVER (
|
||||||
PARTITION BY HT.nHammaddeTuruNo
|
PARTITION BY HT.nHammaddeTuruNo
|
||||||
@@ -1174,18 +1176,19 @@ HammaddeTekil AS (
|
|||||||
AND ISNULL(B.nUrtTipiID, 0) = 1
|
AND ISNULL(B.nUrtTipiID, 0) = 1
|
||||||
WHERE HT.nHammaddeTuruNo IS NOT NULL
|
WHERE HT.nHammaddeTuruNo IS NOT NULL
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
HT.sAciklama3,
|
HT.sAciklama3,
|
||||||
0.0 AS GroupTotalTutar,
|
0.0 AS GroupTotalTutar,
|
||||||
0.0 AS GroupTotalUSDTutar,
|
0.0 AS GroupTotalUSDTutar,
|
||||||
'' AS nOnMLNo,
|
'' AS nOnMLNo,
|
||||||
RTRIM(CONVERT(VARCHAR(32), ISNULL(HT.rowNo, 0))) AS nOnMLDetNo,
|
RTRIM(CONVERT(VARCHAR(32), ISNULL(HT.rowNo, 0))) AS nOnMLDetNo,
|
||||||
HT.nHammaddeTuruNo,
|
HT.nHammaddeTuruNo,
|
||||||
HT.sKodu,
|
HT.nUrtMTBolumID,
|
||||||
HT.sAciklama,
|
HT.sKodu,
|
||||||
HT.sRenk AS sRenk,
|
HT.sAciklama,
|
||||||
'' AS sBeden,
|
HT.sRenk AS sRenk,
|
||||||
'' AS sAciklama2,
|
'' AS sBeden,
|
||||||
|
'' AS sAciklama2,
|
||||||
HT.lMiktar,
|
HT.lMiktar,
|
||||||
0.0 AS lFiyat,
|
0.0 AS lFiyat,
|
||||||
0.0 AS lTutar,
|
0.0 AS lTutar,
|
||||||
|
|||||||
@@ -330,6 +330,7 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
nOnMLNoStr string
|
nOnMLNoStr string
|
||||||
nOnMLDetNoStr string
|
nOnMLDetNoStr string
|
||||||
hNoStr string
|
hNoStr string
|
||||||
|
mtBolumStr string
|
||||||
fiyatGirilen sql.NullFloat64
|
fiyatGirilen sql.NullFloat64
|
||||||
fiyatDoviz sql.NullString
|
fiyatDoviz sql.NullString
|
||||||
maliyeteDahil sql.NullBool
|
maliyeteDahil sql.NullBool
|
||||||
@@ -344,6 +345,7 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
&nOnMLNoStr,
|
&nOnMLNoStr,
|
||||||
&nOnMLDetNoStr,
|
&nOnMLDetNoStr,
|
||||||
&hNoStr,
|
&hNoStr,
|
||||||
|
&mtBolumStr,
|
||||||
&item.SKodu,
|
&item.SKodu,
|
||||||
&item.SAciklama,
|
&item.SAciklama,
|
||||||
&item.SRenk,
|
&item.SRenk,
|
||||||
@@ -378,6 +380,7 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
item.NOnMLNo = strings.TrimSpace(nOnMLNoStr)
|
item.NOnMLNo = strings.TrimSpace(nOnMLNoStr)
|
||||||
item.NOnMLDetNo = strings.TrimSpace(nOnMLDetNoStr)
|
item.NOnMLDetNo = strings.TrimSpace(nOnMLDetNoStr)
|
||||||
item.NHammaddeTuruNo = strings.TrimSpace(hNoStr)
|
item.NHammaddeTuruNo = strings.TrimSpace(hNoStr)
|
||||||
|
item.NUrtMTBolumID = strings.TrimSpace(mtBolumStr)
|
||||||
|
|
||||||
if fiyatGirilen.Valid {
|
if fiyatGirilen.Valid {
|
||||||
item.FiyatGirilen = new(float64)
|
item.FiyatGirilen = new(float64)
|
||||||
@@ -453,6 +456,7 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
nOnMLNoStr string
|
nOnMLNoStr string
|
||||||
nOnMLDetNoStr string
|
nOnMLDetNoStr string
|
||||||
hNoStr string
|
hNoStr string
|
||||||
|
mtBolumStr string
|
||||||
fiyatGirilen sql.NullFloat64
|
fiyatGirilen sql.NullFloat64
|
||||||
fiyatDoviz sql.NullString
|
fiyatDoviz sql.NullString
|
||||||
maliyeteDahil sql.NullBool
|
maliyeteDahil sql.NullBool
|
||||||
@@ -467,6 +471,7 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
&nOnMLNoStr,
|
&nOnMLNoStr,
|
||||||
&nOnMLDetNoStr,
|
&nOnMLDetNoStr,
|
||||||
&hNoStr,
|
&hNoStr,
|
||||||
|
&mtBolumStr,
|
||||||
&item.SKodu,
|
&item.SKodu,
|
||||||
&item.SAciklama,
|
&item.SAciklama,
|
||||||
&item.SRenk,
|
&item.SRenk,
|
||||||
@@ -501,6 +506,7 @@ func GetProductionHasCostDetailGroupsHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
item.NOnMLNo = strings.TrimSpace(nOnMLNoStr)
|
item.NOnMLNo = strings.TrimSpace(nOnMLNoStr)
|
||||||
item.NOnMLDetNo = strings.TrimSpace(nOnMLDetNoStr)
|
item.NOnMLDetNo = strings.TrimSpace(nOnMLDetNoStr)
|
||||||
item.NHammaddeTuruNo = strings.TrimSpace(hNoStr)
|
item.NHammaddeTuruNo = strings.TrimSpace(hNoStr)
|
||||||
|
item.NUrtMTBolumID = strings.TrimSpace(mtBolumStr)
|
||||||
|
|
||||||
if fiyatGirilen.Valid {
|
if fiyatGirilen.Valid {
|
||||||
item.FiyatGirilen = new(float64)
|
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))
|
logger.Info("tx step", "trace_id", traceID, "n_onml_no", nOnMLNo, "step", "detail_upserts", "count", len(req.Detail.Upserts))
|
||||||
skippedUpserts := 0
|
skippedUpserts := 0
|
||||||
skippedUpsertsSample := 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 {
|
for _, row := range req.Detail.Upserts {
|
||||||
if row.NOnMLDetNo <= 0 {
|
if row.NOnMLDetNo <= 0 {
|
||||||
skippedUpserts += 1
|
skippedUpserts += 1
|
||||||
@@ -1537,10 +1551,74 @@ func PostProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http.
|
|||||||
return
|
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
|
qty := row.LMiktar
|
||||||
if qty < 0 {
|
if qty < 0 {
|
||||||
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))
|
cur := strings.ToUpper(strings.TrimSpace(row.FiyatDoviz))
|
||||||
in := row.FiyatGirilen
|
in := row.FiyatGirilen
|
||||||
unitTRY := in
|
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)
|
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.
|
// Recipe sync (spUrtRecMBolum): insert missing rows, update qty when changed.
|
||||||
// This costing screen is the source of truth only for dbo.spUrtOnMLMas / dbo.spUrtOnMLMasDet.
|
// 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")
|
logger.Info("tx step", "trace_id", traceID, "n_onml_no", nOnMLNo, "step", "commit")
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ func loadHasCostDetailGroups(ctx context.Context, uretimDB *sql.DB, nOnMLNo int)
|
|||||||
nOnMLNoStr string
|
nOnMLNoStr string
|
||||||
nOnMLDetNoStr string
|
nOnMLDetNoStr string
|
||||||
hNoStr string
|
hNoStr string
|
||||||
|
mtBolumStr string
|
||||||
fiyatGirilen sql.NullFloat64
|
fiyatGirilen sql.NullFloat64
|
||||||
fiyatDoviz sql.NullString
|
fiyatDoviz sql.NullString
|
||||||
maliyeteDahil sql.NullBool
|
maliyeteDahil sql.NullBool
|
||||||
@@ -150,6 +151,7 @@ func loadHasCostDetailGroups(ctx context.Context, uretimDB *sql.DB, nOnMLNo int)
|
|||||||
&nOnMLNoStr,
|
&nOnMLNoStr,
|
||||||
&nOnMLDetNoStr,
|
&nOnMLDetNoStr,
|
||||||
&hNoStr,
|
&hNoStr,
|
||||||
|
&mtBolumStr,
|
||||||
&item.SKodu,
|
&item.SKodu,
|
||||||
&item.SAciklama,
|
&item.SAciklama,
|
||||||
&item.SRenk,
|
&item.SRenk,
|
||||||
@@ -179,6 +181,7 @@ func loadHasCostDetailGroups(ctx context.Context, uretimDB *sql.DB, nOnMLNo int)
|
|||||||
item.NOnMLNo = strings.TrimSpace(nOnMLNoStr)
|
item.NOnMLNo = strings.TrimSpace(nOnMLNoStr)
|
||||||
item.NOnMLDetNo = strings.TrimSpace(nOnMLDetNoStr)
|
item.NOnMLDetNo = strings.TrimSpace(nOnMLDetNoStr)
|
||||||
item.NHammaddeTuruNo = strings.TrimSpace(hNoStr)
|
item.NHammaddeTuruNo = strings.TrimSpace(hNoStr)
|
||||||
|
item.NUrtMTBolumID = strings.TrimSpace(mtBolumStr)
|
||||||
if fiyatGirilen.Valid {
|
if fiyatGirilen.Valid {
|
||||||
item.FiyatGirilen = new(float64)
|
item.FiyatGirilen = new(float64)
|
||||||
*item.FiyatGirilen = fiyatGirilen.Float64
|
*item.FiyatGirilen = fiyatGirilen.Float64
|
||||||
@@ -248,7 +251,11 @@ func (c *costingPDF) drawHeaderFull() {
|
|||||||
line2 := fmt.Sprintf("Urun: %s - %s", strings.TrimSpace(c.header.UrunKodu), strings.TrimSpace(c.header.UrunAdi))
|
line2 := fmt.Sprintf("Urun: %s - %s", strings.TrimSpace(c.header.UrunKodu), strings.TrimSpace(c.header.UrunAdi))
|
||||||
pdf.CellFormat(0, 5, line2, "", 1, "L", false, 0, "")
|
pdf.CellFormat(0, 5, line2, "", 1, "L", false, 0, "")
|
||||||
|
|
||||||
line3 := fmt.Sprintf("Firma: %s | Kaydeden: %s | Guncelleme: %s (%s)", strings.TrimSpace(c.header.FirmaKodu), strings.TrimSpace(c.header.SKullaniciAdi), strings.TrimSpace(c.header.DteGuncellemeTarihi), strings.TrimSpace(c.header.SGuncellemeKullaniciAdi))
|
firmaLabel := strings.TrimSpace(c.header.FirmaKodu)
|
||||||
|
if strings.TrimSpace(c.header.UretimiYapanFirma) != "" {
|
||||||
|
firmaLabel = fmt.Sprintf("%s - %s", firmaLabel, strings.TrimSpace(c.header.UretimiYapanFirma))
|
||||||
|
}
|
||||||
|
line3 := fmt.Sprintf("Firma: %s | Kaydeden: %s | Guncelleme: %s (%s)", firmaLabel, strings.TrimSpace(c.header.SKullaniciAdi), strings.TrimSpace(c.header.DteGuncellemeTarihi), strings.TrimSpace(c.header.SGuncellemeKullaniciAdi))
|
||||||
pdf.CellFormat(0, 5, line3, "", 1, "L", false, 0, "")
|
pdf.CellFormat(0, 5, line3, "", 1, "L", false, 0, "")
|
||||||
|
|
||||||
pdf.SetTextColor(0, 0, 0)
|
pdf.SetTextColor(0, 0, 0)
|
||||||
@@ -258,7 +265,11 @@ func (c *costingPDF) drawHeaderFull() {
|
|||||||
func (c *costingPDF) drawHeaderCompact() {
|
func (c *costingPDF) drawHeaderCompact() {
|
||||||
pdf := c.pdf
|
pdf := c.pdf
|
||||||
pdf.SetFont("dejavu", "B", 10.5)
|
pdf.SetFont("dejavu", "B", 10.5)
|
||||||
title := fmt.Sprintf("OnML %s | %s - %s | %s", c.header.NOnMLNo, strings.TrimSpace(c.header.UrunKodu), strings.TrimSpace(c.header.UrunAdi), c.header.DteKayitTarihi)
|
firmaLabel := strings.TrimSpace(c.header.FirmaKodu)
|
||||||
|
if strings.TrimSpace(c.header.UretimiYapanFirma) != "" {
|
||||||
|
firmaLabel = fmt.Sprintf("%s - %s", firmaLabel, strings.TrimSpace(c.header.UretimiYapanFirma))
|
||||||
|
}
|
||||||
|
title := fmt.Sprintf("OnML %s | %s - %s | %s | %s", c.header.NOnMLNo, strings.TrimSpace(c.header.UrunKodu), strings.TrimSpace(c.header.UrunAdi), c.header.DteKayitTarihi, firmaLabel)
|
||||||
pdf.CellFormat(0, 6, title, "", 1, "L", false, 0, "")
|
pdf.CellFormat(0, 6, title, "", 1, "L", false, 0, "")
|
||||||
pdf.Ln(1)
|
pdf.Ln(1)
|
||||||
}
|
}
|
||||||
@@ -270,8 +281,23 @@ func (c *costingPDF) drawGroup(g models.ProductionHasCostDetailGroup, firstGroup
|
|||||||
c.drawGroupBar(g, false)
|
c.drawGroupBar(g, false)
|
||||||
|
|
||||||
// Columns
|
// Columns
|
||||||
cols := []string{"No", "Parca", "Hammadde", "Kod", "Aciklama", "Renk", "Miktar", "Br", "Fiyat", "Pr.Br", "Tutar(TRY)"}
|
// Keep total width <= A4 landscape printable width (297 - left/right margins).
|
||||||
wn := []float64{10, 24, 24, 40, 90, 18, 18, 12, 20, 14, 24} // sum ~294 (A4 landscape width minus margins)
|
// Also force USD/TRY unit+total columns to always be visible.
|
||||||
|
cols := []string{
|
||||||
|
"No",
|
||||||
|
"Parca",
|
||||||
|
"Hammadde",
|
||||||
|
"Kod",
|
||||||
|
"Aciklama",
|
||||||
|
"Renk",
|
||||||
|
"Miktar",
|
||||||
|
"Br",
|
||||||
|
"USD\nFiyat",
|
||||||
|
"USD\nTutar",
|
||||||
|
"TRY\nFiyat",
|
||||||
|
"TRY\nTutar",
|
||||||
|
}
|
||||||
|
wn := []float64{8, 20, 22, 32, 70, 14, 14, 10, 16, 16, 16, 16} // sum = 250
|
||||||
|
|
||||||
c.drawTableHeader(cols, wn)
|
c.drawTableHeader(cols, wn)
|
||||||
for _, it := range g.Items {
|
for _, it := range g.Items {
|
||||||
@@ -297,10 +323,29 @@ func (c *costingPDF) drawTableHeader(cols []string, wn []float64) {
|
|||||||
pdf.SetFont("dejavu", "B", 8)
|
pdf.SetFont("dejavu", "B", 8)
|
||||||
pdf.SetFillColor(30, 30, 30)
|
pdf.SetFillColor(30, 30, 30)
|
||||||
pdf.SetTextColor(255, 255, 255)
|
pdf.SetTextColor(255, 255, 255)
|
||||||
|
|
||||||
|
// Compute a stable header height based on wrapped labels.
|
||||||
|
maxLines := 1
|
||||||
for i, col := range cols {
|
for i, col := range cols {
|
||||||
pdf.CellFormat(wn[i], 5.5, col, "1", 0, "C", true, 0, "")
|
lines := pdf.SplitLines([]byte(col), wn[i]-1.6)
|
||||||
|
if len(lines) > maxLines {
|
||||||
|
maxLines = len(lines)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pdf.Ln(5.5)
|
lineH := 3.5
|
||||||
|
headerH := float64(maxLines)*lineH + 1.2
|
||||||
|
if headerH < 7.0 {
|
||||||
|
headerH = 7.0
|
||||||
|
}
|
||||||
|
|
||||||
|
x0 := pdf.GetX()
|
||||||
|
y0 := pdf.GetY()
|
||||||
|
x := x0
|
||||||
|
for i, col := range cols {
|
||||||
|
c.drawHeaderCellWrap(x, y0, wn[i], headerH, col)
|
||||||
|
x += wn[i]
|
||||||
|
}
|
||||||
|
pdf.SetXY(x0, y0+headerH)
|
||||||
pdf.SetTextColor(0, 0, 0)
|
pdf.SetTextColor(0, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,9 +364,28 @@ func (c *costingPDF) drawRowWithGroup(it models.ProductionHasCostDetailGroupItem
|
|||||||
pdf := c.pdf
|
pdf := c.pdf
|
||||||
pdf.SetFont("dejavu", "", 7.2)
|
pdf.SetFont("dejavu", "", 7.2)
|
||||||
|
|
||||||
// Compute row height based on description wrapping.
|
// Compute row height based on key wrapped cells.
|
||||||
|
parcaLines := pdf.SplitLines([]byte(strings.TrimSpace(it.SParcaAdi)), wn[1]-2)
|
||||||
|
hLabel := strings.TrimSpace(it.NHammaddeTuruNo)
|
||||||
|
if strings.TrimSpace(it.SHammaddeTuruAdi) != "" {
|
||||||
|
hLabel = hLabel + " " + strings.TrimSpace(it.SHammaddeTuruAdi)
|
||||||
|
}
|
||||||
|
hLines := pdf.SplitLines([]byte(hLabel), wn[2]-2)
|
||||||
|
kodLines := pdf.SplitLines([]byte(strings.TrimSpace(it.SKodu)), wn[3]-2)
|
||||||
descLines := pdf.SplitLines([]byte(strings.TrimSpace(it.SAciklama)), wn[4]-2)
|
descLines := pdf.SplitLines([]byte(strings.TrimSpace(it.SAciklama)), wn[4]-2)
|
||||||
rowH := float64(len(descLines)) * 3.5
|
|
||||||
|
maxLines := len(descLines)
|
||||||
|
if len(parcaLines) > maxLines {
|
||||||
|
maxLines = len(parcaLines)
|
||||||
|
}
|
||||||
|
if len(hLines) > maxLines {
|
||||||
|
maxLines = len(hLines)
|
||||||
|
}
|
||||||
|
if len(kodLines) > maxLines {
|
||||||
|
maxLines = len(kodLines)
|
||||||
|
}
|
||||||
|
|
||||||
|
rowH := float64(maxLines) * 3.5
|
||||||
if rowH < 5.0 {
|
if rowH < 5.0 {
|
||||||
rowH = 5.0
|
rowH = 5.0
|
||||||
}
|
}
|
||||||
@@ -336,15 +400,11 @@ func (c *costingPDF) drawRowWithGroup(it models.ProductionHasCostDetailGroupItem
|
|||||||
|
|
||||||
c.drawCell(x0, y0, wn[0], rowH, it.NOnMLDetNo, "R")
|
c.drawCell(x0, y0, wn[0], rowH, it.NOnMLDetNo, "R")
|
||||||
x := x0 + wn[0]
|
x := x0 + wn[0]
|
||||||
c.drawCell(x, y0, wn[1], rowH, strings.TrimSpace(it.SParcaAdi), "L")
|
c.drawCellWrap(x, y0, wn[1], rowH, strings.TrimSpace(it.SParcaAdi), "L")
|
||||||
x += wn[1]
|
x += wn[1]
|
||||||
hLabel := strings.TrimSpace(it.NHammaddeTuruNo)
|
c.drawCellWrap(x, y0, wn[2], rowH, hLabel, "L")
|
||||||
if strings.TrimSpace(it.SHammaddeTuruAdi) != "" {
|
|
||||||
hLabel = hLabel + " " + strings.TrimSpace(it.SHammaddeTuruAdi)
|
|
||||||
}
|
|
||||||
c.drawCell(x, y0, wn[2], rowH, hLabel, "L")
|
|
||||||
x += wn[2]
|
x += wn[2]
|
||||||
c.drawCell(x, y0, wn[3], rowH, strings.TrimSpace(it.SKodu), "L")
|
c.drawCellWrap(x, y0, wn[3], rowH, strings.TrimSpace(it.SKodu), "L")
|
||||||
x += wn[3]
|
x += wn[3]
|
||||||
c.drawCellWrap(x, y0, wn[4], rowH, strings.TrimSpace(it.SAciklama), "L")
|
c.drawCellWrap(x, y0, wn[4], rowH, strings.TrimSpace(it.SAciklama), "L")
|
||||||
x += wn[4]
|
x += wn[4]
|
||||||
@@ -355,24 +415,38 @@ func (c *costingPDF) drawRowWithGroup(it models.ProductionHasCostDetailGroupItem
|
|||||||
c.drawCell(x, y0, wn[7], rowH, strings.TrimSpace(it.SBirim), "C")
|
c.drawCell(x, y0, wn[7], rowH, strings.TrimSpace(it.SBirim), "C")
|
||||||
x += wn[7]
|
x += wn[7]
|
||||||
|
|
||||||
// Prefer input price if present; otherwise lFiyat.
|
// Always show USD/TRY unit+total.
|
||||||
price := it.LFiyat
|
// In URETIM schema: lFiyat/lTutar are in TRY, lDovizFiyati/usdTutar are in USD.
|
||||||
cur := strings.TrimSpace(it.SDovizCinsi)
|
c.drawCell(x, y0, wn[8], rowH, pdfMoney(it.LDovizFiyati), "R")
|
||||||
if it.FiyatGirilen != nil && *it.FiyatGirilen > 0 {
|
|
||||||
price = *it.FiyatGirilen
|
|
||||||
if strings.TrimSpace(it.FiyatDoviz) != "" {
|
|
||||||
cur = strings.TrimSpace(it.FiyatDoviz)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.drawCell(x, y0, wn[8], rowH, pdfMoney(price), "R")
|
|
||||||
x += wn[8]
|
x += wn[8]
|
||||||
c.drawCell(x, y0, wn[9], rowH, cur, "C")
|
usdTotal := it.USDTutar
|
||||||
|
if usdTotal == 0 && it.LMiktar != 0 && it.LDovizFiyati != 0 {
|
||||||
|
usdTotal = it.LMiktar * it.LDovizFiyati
|
||||||
|
}
|
||||||
|
c.drawCell(x, y0, wn[9], rowH, pdfMoney(usdTotal), "R")
|
||||||
x += wn[9]
|
x += wn[9]
|
||||||
c.drawCell(x, y0, wn[10], rowH, pdfMoney(it.LTutar), "R")
|
|
||||||
|
// Prefer input price if present; otherwise lFiyat.
|
||||||
|
unitTRY := it.LFiyat
|
||||||
|
if it.FiyatGirilen != nil && *it.FiyatGirilen > 0 && strings.EqualFold(strings.TrimSpace(it.FiyatDoviz), "TRY") {
|
||||||
|
unitTRY = *it.FiyatGirilen
|
||||||
|
}
|
||||||
|
c.drawCell(x, y0, wn[10], rowH, pdfMoney(unitTRY), "R")
|
||||||
|
x += wn[10]
|
||||||
|
c.drawCell(x, y0, wn[11], rowH, pdfMoney(it.LTutar), "R")
|
||||||
|
|
||||||
pdf.SetXY(x0, y0+rowH)
|
pdf.SetXY(x0, y0+rowH)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *costingPDF) drawHeaderCellWrap(x, y, w, h float64, txt string) {
|
||||||
|
pdf := c.pdf
|
||||||
|
pdf.Rect(x, y, w, h, "DF")
|
||||||
|
pdf.SetXY(x+0.8, y+0.6)
|
||||||
|
pdf.MultiCell(w-1.6, 3.5, txt, "", "C", true)
|
||||||
|
// restore cursor (MultiCell moves Y)
|
||||||
|
pdf.SetXY(x+w, y)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *costingPDF) drawCell(x, y, w, h float64, txt, align string) {
|
func (c *costingPDF) drawCell(x, y, w, h float64, txt, align string) {
|
||||||
pdf := c.pdf
|
pdf := c.pdf
|
||||||
pdf.Rect(x, y, w, h, "")
|
pdf.Rect(x, y, w, h, "")
|
||||||
|
|||||||
@@ -254,7 +254,7 @@
|
|||||||
{{ grp.sAciklama3 || 'TANIMSIZ' }}
|
{{ grp.sAciklama3 || 'TANIMSIZ' }}
|
||||||
</div>
|
</div>
|
||||||
<div class="sub-right pcd-sub-right-clickable" @click="toggleGroup(grp, gi)">
|
<div class="sub-right pcd-sub-right-clickable" @click="toggleGroup(grp, gi)">
|
||||||
<span v-if="normalizeGroupName(grp.sAciklama3) === 'FABRIC'" class="q-mr-sm">
|
<span v-if="normalizeGroupName(grp.sAciklama3) === 'FABRIC'" class="pcd-sub-mt-qty">
|
||||||
Toplam Miktar: {{ formatBarQuantity(resolveGroupQuantity(grp)) }} MT |
|
Toplam Miktar: {{ formatBarQuantity(resolveGroupQuantity(grp)) }} MT |
|
||||||
</span>
|
</span>
|
||||||
Grup Toplami TRY: {{ formatBarMoney(resolveGroupTRYTutar(grp)) }} | USD: {{ formatBarMoney(resolveGroupUSDTutar(grp)) }}
|
Grup Toplami TRY: {{ formatBarMoney(resolveGroupTRYTutar(grp)) }} | USD: {{ formatBarMoney(resolveGroupUSDTutar(grp)) }}
|
||||||
@@ -894,6 +894,7 @@ const lineHistoryTargetSummary = ref('')
|
|||||||
const lineHistorySearchMode = ref('exact')
|
const lineHistorySearchMode = ref('exact')
|
||||||
const lineHistoryLastPurchaseMatchStage = ref('')
|
const lineHistoryLastPurchaseMatchStage = ref('')
|
||||||
const lineHistoryLastRecipeMatchStage = ref('')
|
const lineHistoryLastRecipeMatchStage = ref('')
|
||||||
|
const purchaseAvgUSDCachedByCode = ref({})
|
||||||
const headerInfoCollapsed = ref(false)
|
const headerInfoCollapsed = ref(false)
|
||||||
const subHeaderTop = ref(140)
|
const subHeaderTop = ref(140)
|
||||||
const stickyStackRef = ref(null)
|
const stickyStackRef = ref(null)
|
||||||
@@ -1299,6 +1300,35 @@ function formatBarQuantity (value) {
|
|||||||
return formatQuantity(roundedValue)
|
return formatQuantity(roundedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function convertPriceToUSD (price, currency) {
|
||||||
|
const p = Number(price || 0)
|
||||||
|
if (!Number.isFinite(p) || p <= 0) return 0
|
||||||
|
const cur = String(currency || '').trim().toUpperCase()
|
||||||
|
const usdRate = Number(exchangeRates.value?.usdRate || 0) || 0
|
||||||
|
const eurRate = Number(exchangeRates.value?.eurRate || 0) || 0
|
||||||
|
const gbpRate = Number(exchangeRates.value?.gbpRate || 0) || 0
|
||||||
|
// If we don't have rates, fall back to assuming USD.
|
||||||
|
if (!(usdRate > 0)) return p
|
||||||
|
switch (cur) {
|
||||||
|
case 'USD':
|
||||||
|
return p
|
||||||
|
case 'TRY':
|
||||||
|
case 'TL':
|
||||||
|
case '':
|
||||||
|
return p / usdRate
|
||||||
|
case 'EUR': {
|
||||||
|
const tryVal = (eurRate > 0 ? p * eurRate : p)
|
||||||
|
return tryVal / usdRate
|
||||||
|
}
|
||||||
|
case 'GBP': {
|
||||||
|
const tryVal = (gbpRate > 0 ? p * gbpRate : p)
|
||||||
|
return tryVal / usdRate
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function normalizePriceCurrency (value) {
|
function normalizePriceCurrency (value) {
|
||||||
const normalizedValue = String(value || '').trim().toUpperCase()
|
const normalizedValue = String(value || '').trim().toUpperCase()
|
||||||
return ['USD', 'TRY', 'EUR', 'GBP'].includes(normalizedValue) ? normalizedValue : ''
|
return ['USD', 'TRY', 'EUR', 'GBP'].includes(normalizedValue) ? normalizedValue : ''
|
||||||
@@ -3881,6 +3911,142 @@ async function confirmDefaultQtyDeviationIfNeeded () {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getPurchaseAvgUSDForCode (code) {
|
||||||
|
const normalized = String(code || '').trim()
|
||||||
|
if (!normalized) return { ok: false, code: '', avgUSD: 0, n: 0 }
|
||||||
|
const cached = purchaseAvgUSDCachedByCode.value?.[normalized]
|
||||||
|
if (cached && cached.ok) return cached
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await get('/pricing/production-product-costing/has-cost-detail-line-history', {
|
||||||
|
n_onml_no: parseInt(String(onMLNo.value || detailHeader.value?.nOnMLNo || detailHeader.value?.NOnMLNo || '0'), 10) || 0,
|
||||||
|
s_kodu: normalized,
|
||||||
|
maliyet_tarihi: normalizeDateInput(costDate.value),
|
||||||
|
trace_id: traceId.value
|
||||||
|
})
|
||||||
|
const rows = Array.isArray(response?.purchaseRows) ? response.purchaseRows : []
|
||||||
|
const picked = []
|
||||||
|
for (const r of rows) {
|
||||||
|
const p = Number(r?.EvrakFiyat || 0)
|
||||||
|
if (!(p > 0)) continue
|
||||||
|
const cur = String(r?.EvrakDoviz || '').trim().toUpperCase() || 'USD'
|
||||||
|
const usd = convertPriceToUSD(p, cur)
|
||||||
|
if (!(usd > 0)) continue
|
||||||
|
picked.push(usd)
|
||||||
|
if (picked.length >= 10) break
|
||||||
|
}
|
||||||
|
const avgUSD = picked.length > 0 ? (picked.reduce((a, b) => a + b, 0) / picked.length) : 0
|
||||||
|
const result = { ok: picked.length > 0, code: normalized, avgUSD, n: picked.length }
|
||||||
|
purchaseAvgUSDCachedByCode.value = { ...(purchaseAvgUSDCachedByCode.value || {}), [normalized]: result }
|
||||||
|
return result
|
||||||
|
} catch (e) {
|
||||||
|
const result = { ok: false, code: normalized, avgUSD: 0, n: 0, error: String(e?.message || e) }
|
||||||
|
purchaseAvgUSDCachedByCode.value = { ...(purchaseAvgUSDCachedByCode.value || {}), [normalized]: result }
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmBrPriceDeviationIfNeeded () {
|
||||||
|
const rows = Array.isArray(flatDetailRows.value) ? flatDetailRows.value : []
|
||||||
|
if (rows.length === 0) return true
|
||||||
|
|
||||||
|
// Only consider rows that have a code + a non-zero entered price.
|
||||||
|
const candidates = rows
|
||||||
|
.filter(r => String(r?.sKodu || '').trim() !== '')
|
||||||
|
.map(r => {
|
||||||
|
const price = Number(resolveNumericRowInputPrice(r) || 0)
|
||||||
|
const cur = String(resolveInputCurrency(r) || '').trim().toUpperCase() || 'USD'
|
||||||
|
return { row: r, code: String(r?.sKodu || '').trim(), price, cur }
|
||||||
|
})
|
||||||
|
.filter(x => x.price > 0)
|
||||||
|
|
||||||
|
if (candidates.length === 0) return true
|
||||||
|
|
||||||
|
const uniqueCodes = Array.from(new Set(candidates.map(x => x.code)))
|
||||||
|
// Fetch averages with simple batching to avoid hammering the API.
|
||||||
|
const avgByCode = {}
|
||||||
|
const batchSize = 8
|
||||||
|
for (let i = 0; i < uniqueCodes.length; i += batchSize) {
|
||||||
|
const batch = uniqueCodes.slice(i, i + batchSize)
|
||||||
|
const results = await Promise.all(batch.map(c => getPurchaseAvgUSDForCode(c)))
|
||||||
|
results.forEach(r => { avgByCode[r.code] = r })
|
||||||
|
}
|
||||||
|
|
||||||
|
const outliers = []
|
||||||
|
for (const c of candidates) {
|
||||||
|
const avg = avgByCode[c.code]
|
||||||
|
if (!avg || !avg.ok || !(avg.avgUSD > 0) || avg.n < 3) continue // too little history -> ignore
|
||||||
|
const enteredUSD = convertPriceToUSD(c.price, c.cur)
|
||||||
|
if (!(enteredUSD > 0)) continue
|
||||||
|
const pct = ((enteredUSD - avg.avgUSD) / avg.avgUSD) * 100
|
||||||
|
if (Math.abs(pct) > 10) {
|
||||||
|
outliers.push({
|
||||||
|
code: c.code,
|
||||||
|
avgUSD: avg.avgUSD,
|
||||||
|
enteredUSD,
|
||||||
|
pct
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outliers.length === 0) return true
|
||||||
|
|
||||||
|
outliers.sort((a, b) => Math.abs(b.pct) - Math.abs(a.pct))
|
||||||
|
const maxRows = 30
|
||||||
|
const rowsHtml = outliers.slice(0, maxRows).map(x => {
|
||||||
|
const sign = x.pct >= 0 ? '+' : ''
|
||||||
|
const pct = `${sign}${round1(x.pct)}%`
|
||||||
|
const cls = x.pct >= 0 ? 'color:#b71c1c;' : 'color:#1b5e20;'
|
||||||
|
return `
|
||||||
|
<tr>
|
||||||
|
<td style="padding:6px 8px; white-space:nowrap; font-weight:600;">${escapeHtml(x.code)}</td>
|
||||||
|
<td style="padding:6px 8px; text-align:right; white-space:nowrap;">${formatMoney(x.avgUSD)}</td>
|
||||||
|
<td style="padding:6px 8px; text-align:right; white-space:nowrap;">${formatMoney(x.enteredUSD)}</td>
|
||||||
|
<td style="padding:6px 8px; text-align:right; white-space:nowrap; ${cls} font-weight:600;">${pct}</td>
|
||||||
|
</tr>
|
||||||
|
`
|
||||||
|
}).join('')
|
||||||
|
|
||||||
|
const truncatedNote = outliers.length > maxRows
|
||||||
|
? `<div style="margin-top:8px; color:#666;">Toplam ${outliers.length} satir var. Ilk ${maxRows} gosterildi.</div>`
|
||||||
|
: ''
|
||||||
|
|
||||||
|
const ok = await new Promise(resolve => {
|
||||||
|
$q.dialog({
|
||||||
|
title: 'Fiyat Kontrolu (Satinalma Ortalama)',
|
||||||
|
html: true,
|
||||||
|
message: `
|
||||||
|
<div style="margin-bottom:10px;">
|
||||||
|
Bazı satırlarda girilen fiyat, BAGGI_V3 satınalma geçmişindeki <b>son 10</b> kaydın USD ortalamasından <b>%10</b>'dan fazla sapıyor.
|
||||||
|
</div>
|
||||||
|
<div style="max-height: 360px; overflow:auto; border:1px solid #e0e0e0; border-radius:6px;">
|
||||||
|
<table style="width:100%; border-collapse:collapse; font-size:13px;">
|
||||||
|
<thead>
|
||||||
|
<tr style="background:#f5f5f5; position: sticky; top: 0;">
|
||||||
|
<th style="text-align:left; padding:6px 8px;">Kod</th>
|
||||||
|
<th style="text-align:right; padding:6px 8px;">Ort USD</th>
|
||||||
|
<th style="text-align:right; padding:6px 8px;">Girilen USD</th>
|
||||||
|
<th style="text-align:right; padding:6px 8px;">Fark %</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${rowsHtml}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
${truncatedNote}
|
||||||
|
<div style="margin-top:10px;">
|
||||||
|
Onayliyorsaniz <b>Onayla ve Kaydet</b>'e basın. Duzenlemek icin <b>Geri Don</b>.
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
cancel: { label: 'Geri Don' },
|
||||||
|
ok: { label: 'Onayla ve Kaydet', color: 'primary' },
|
||||||
|
persistent: true
|
||||||
|
}).onOk(() => resolve(true)).onCancel(() => resolve(false))
|
||||||
|
})
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
async function deleteCosting () {
|
async function deleteCosting () {
|
||||||
if (!detailHeader.value) return
|
if (!detailHeader.value) return
|
||||||
const n = parseInt(String(detailHeader.value?.nOnMLNo || detailHeader.value?.NOnMLNo || onMLNo.value || '0'), 10) || 0
|
const n = parseInt(String(detailHeader.value?.nOnMLNo || detailHeader.value?.NOnMLNo || onMLNo.value || '0'), 10) || 0
|
||||||
@@ -4030,6 +4196,9 @@ async function saveChanges () {
|
|||||||
const okDefaultQty = await confirmDefaultQtyDeviationIfNeeded()
|
const okDefaultQty = await confirmDefaultQtyDeviationIfNeeded()
|
||||||
if (!okDefaultQty) return
|
if (!okDefaultQty) return
|
||||||
|
|
||||||
|
const okBrPrice = await confirmBrPriceDeviationIfNeeded()
|
||||||
|
if (!okBrPrice) return
|
||||||
|
|
||||||
if (!detailHeader.value) {
|
if (!detailHeader.value) {
|
||||||
$q.notify({ type: 'negative', message: 'Header bulunamadi.', position: 'top-right' })
|
$q.notify({ type: 'negative', message: 'Header bulunamadi.', position: 'top-right' })
|
||||||
return
|
return
|
||||||
@@ -4507,6 +4676,10 @@ watch(
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
.pcd-sub-right-clickable {
|
.pcd-sub-right-clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -4514,6 +4687,13 @@ watch(
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.pcd-sub-mt-qty {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pcd-detail-table :deep(.q-table__middle) {
|
.pcd-detail-table :deep(.q-table__middle) {
|
||||||
|
|||||||
Reference in New Issue
Block a user