diff --git a/svc/models/production_product_costing.go b/svc/models/production_product_costing.go index f49b28c..0ddf815 100644 --- a/svc/models/production_product_costing.go +++ b/svc/models/production_product_costing.go @@ -61,6 +61,7 @@ type ProductionHasCostDetailGroupItem struct { NOnMLNo string `json:"nOnMLNo"` NOnMLDetNo string `json:"nOnMLDetNo"` NHammaddeTuruNo string `json:"nHammaddeTuruNo"` + NUrtMTBolumID string `json:"nUrtMTBolumID"` SKodu string `json:"sKodu"` SAciklama string `json:"sAciklama"` SRenk string `json:"sRenk"` diff --git a/svc/queries/production_product_costing.go b/svc/queries/production_product_costing.go index e022a09..bfc480d 100644 --- a/svc/queries/production_product_costing.go +++ b/svc/queries/production_product_costing.go @@ -922,12 +922,13 @@ func GetProductionHasCostDetailRowsByOnMLNo( 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')) ) AS GroupTotalUSDTutar, - 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.nHammaddeTuruNo, 0))) AS nHammaddeTuruNo, - -- Normalize code to variantless (tbStok.sModel) when D.sKodu is a variant-coded stock record. - ISNULL(NULLIF(LTRIM(RTRIM(SX.sModel)), ''), ISNULL(D.sKodu, '')) AS sKodu, - ISNULL(NULLIF(LTRIM(RTRIM(SX.sAciklama)), ''), ISNULL(D.sAciklama, '')) AS sAciklama, + 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.nHammaddeTuruNo, 0))) AS nHammaddeTuruNo, + RTRIM(CONVERT(VARCHAR(32), ISNULL(D.nUrtMTBolumID, 0))) AS nUrtMTBolumID, + -- Normalize code to variantless (tbStok.sModel) when D.sKodu is a variant-coded stock record. + 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.sBeden, '') AS sBeden, ISNULL(D.sAciklama2, '') AS sAciklama2, @@ -1141,6 +1142,7 @@ HammaddeTekil AS ( ISNULL(S.sBirimCinsi1, '') AS sBirim, ISNULL(RMik.lHMiktar, 0) AS lMiktar, ISNULL(HT.MTnUrtMTBolumID, 0) AS MTnUrtMTBolumID, + RTRIM(CONVERT(VARCHAR(32), ISNULL(HT.MTnUrtMTBolumID, 0))) AS nUrtMTBolumID, ISNULL(B.sAdi, '') AS sParcaAdi, ROW_NUMBER() OVER ( PARTITION BY HT.nHammaddeTuruNo @@ -1174,18 +1176,19 @@ HammaddeTekil AS ( AND ISNULL(B.nUrtTipiID, 0) = 1 WHERE HT.nHammaddeTuruNo IS NOT NULL ) -SELECT - HT.sAciklama3, - 0.0 AS GroupTotalTutar, - 0.0 AS GroupTotalUSDTutar, - '' AS nOnMLNo, - RTRIM(CONVERT(VARCHAR(32), ISNULL(HT.rowNo, 0))) AS nOnMLDetNo, - HT.nHammaddeTuruNo, - HT.sKodu, - HT.sAciklama, - HT.sRenk AS sRenk, - '' AS sBeden, - '' AS sAciklama2, + SELECT + HT.sAciklama3, + 0.0 AS GroupTotalTutar, + 0.0 AS GroupTotalUSDTutar, + '' AS nOnMLNo, + RTRIM(CONVERT(VARCHAR(32), ISNULL(HT.rowNo, 0))) AS nOnMLDetNo, + HT.nHammaddeTuruNo, + HT.nUrtMTBolumID, + HT.sKodu, + HT.sAciklama, + HT.sRenk AS sRenk, + '' AS sBeden, + '' AS sAciklama2, HT.lMiktar, 0.0 AS lFiyat, 0.0 AS lTutar, diff --git a/svc/routes/production_product_costing.go b/svc/routes/production_product_costing.go index 872f2f8..4b84226 100644 --- a/svc/routes/production_product_costing.go +++ b/svc/routes/production_product_costing.go @@ -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 { diff --git a/svc/routes/production_product_costing_pdf.go b/svc/routes/production_product_costing_pdf.go index 349bbf9..69fadbb 100644 --- a/svc/routes/production_product_costing_pdf.go +++ b/svc/routes/production_product_costing_pdf.go @@ -136,6 +136,7 @@ func loadHasCostDetailGroups(ctx context.Context, uretimDB *sql.DB, nOnMLNo int) nOnMLNoStr string nOnMLDetNoStr string hNoStr string + mtBolumStr string fiyatGirilen sql.NullFloat64 fiyatDoviz sql.NullString maliyeteDahil sql.NullBool @@ -150,6 +151,7 @@ func loadHasCostDetailGroups(ctx context.Context, uretimDB *sql.DB, nOnMLNo int) &nOnMLNoStr, &nOnMLDetNoStr, &hNoStr, + &mtBolumStr, &item.SKodu, &item.SAciklama, &item.SRenk, @@ -179,6 +181,7 @@ func loadHasCostDetailGroups(ctx context.Context, uretimDB *sql.DB, nOnMLNo int) 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) *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)) 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.SetTextColor(0, 0, 0) @@ -258,7 +265,11 @@ func (c *costingPDF) drawHeaderFull() { func (c *costingPDF) drawHeaderCompact() { pdf := c.pdf 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.Ln(1) } @@ -270,8 +281,23 @@ func (c *costingPDF) drawGroup(g models.ProductionHasCostDetailGroup, firstGroup c.drawGroupBar(g, false) // Columns - cols := []string{"No", "Parca", "Hammadde", "Kod", "Aciklama", "Renk", "Miktar", "Br", "Fiyat", "Pr.Br", "Tutar(TRY)"} - wn := []float64{10, 24, 24, 40, 90, 18, 18, 12, 20, 14, 24} // sum ~294 (A4 landscape width minus margins) + // Keep total width <= A4 landscape printable width (297 - left/right 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) for _, it := range g.Items { @@ -297,10 +323,29 @@ func (c *costingPDF) drawTableHeader(cols []string, wn []float64) { pdf.SetFont("dejavu", "B", 8) pdf.SetFillColor(30, 30, 30) pdf.SetTextColor(255, 255, 255) + + // Compute a stable header height based on wrapped labels. + maxLines := 1 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) } @@ -319,9 +364,28 @@ func (c *costingPDF) drawRowWithGroup(it models.ProductionHasCostDetailGroupItem pdf := c.pdf 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) - 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 { rowH = 5.0 } @@ -336,15 +400,11 @@ func (c *costingPDF) drawRowWithGroup(it models.ProductionHasCostDetailGroupItem c.drawCell(x0, y0, wn[0], rowH, it.NOnMLDetNo, "R") 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] - hLabel := strings.TrimSpace(it.NHammaddeTuruNo) - if strings.TrimSpace(it.SHammaddeTuruAdi) != "" { - hLabel = hLabel + " " + strings.TrimSpace(it.SHammaddeTuruAdi) - } - c.drawCell(x, y0, wn[2], rowH, hLabel, "L") + c.drawCellWrap(x, y0, wn[2], rowH, hLabel, "L") 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] c.drawCellWrap(x, y0, wn[4], rowH, strings.TrimSpace(it.SAciklama), "L") 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") x += wn[7] - // Prefer input price if present; otherwise lFiyat. - price := it.LFiyat - cur := strings.TrimSpace(it.SDovizCinsi) - 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") + // Always show USD/TRY unit+total. + // In URETIM schema: lFiyat/lTutar are in TRY, lDovizFiyati/usdTutar are in USD. + c.drawCell(x, y0, wn[8], rowH, pdfMoney(it.LDovizFiyati), "R") 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] - 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) } +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) { pdf := c.pdf pdf.Rect(x, y, w, h, "") diff --git a/ui/src/pages/ProductionProductCostingHasCostDetail.vue b/ui/src/pages/ProductionProductCostingHasCostDetail.vue index a09cea8..0e838d7 100644 --- a/ui/src/pages/ProductionProductCostingHasCostDetail.vue +++ b/ui/src/pages/ProductionProductCostingHasCostDetail.vue @@ -254,7 +254,7 @@ {{ grp.sAciklama3 || 'TANIMSIZ' }}
| Kod | +Ort USD | +Girilen USD | +Fark % | +
|---|