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

@@ -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"`

View File

@@ -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,

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 {

View File

@@ -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, "")