From 9d69a796895793e72c4d3b69d0f15f1e13d330cb Mon Sep 17 00:00:00 2001 From: M_Kececi Date: Wed, 20 May 2026 21:49:12 +0300 Subject: [PATCH] Merge remote-tracking branch 'origin/master' --- svc/models/production_product_costing.go | 1 + svc/queries/production_product_costing.go | 1 + svc/routes/production_product_costing.go | 1 + svc/routes/production_product_costing_pdf.go | 277 +++++++++++++++++-- 4 files changed, 256 insertions(+), 24 deletions(-) diff --git a/svc/models/production_product_costing.go b/svc/models/production_product_costing.go index 0ddf815..6aca02d 100644 --- a/svc/models/production_product_costing.go +++ b/svc/models/production_product_costing.go @@ -106,6 +106,7 @@ type ProductionHasCostDetailHeader struct { UretimSekli string `json:"UretimSekli"` FirmaKodu string `json:"FirmaKodu"` NFirmaID int `json:"nFirmaID"` + MaliyetTarihi string `json:"maliyetTarihi"` DteKayitTarihi string `json:"dteKayitTarihi"` SKullaniciAdi string `json:"sKullaniciAdi"` LTutarTL float64 `json:"lTutarTL"` diff --git a/svc/queries/production_product_costing.go b/svc/queries/production_product_costing.go index bfc480d..a0784a2 100644 --- a/svc/queries/production_product_costing.go +++ b/svc/queries/production_product_costing.go @@ -988,6 +988,7 @@ SELECT TOP 1 ISNULL(M.UrunAdi, '') AS UrunAdi, RTRIM(CONVERT(VARCHAR(32), ISNULL(M.uretim_sekli_id, 0))) AS uretim_sekli_id, ISNULL(US.aciklama, '') AS uretim_sekli, + CONVERT(VARCHAR(10), M.Tarihi, 120) AS maliyetTarihi, CONVERT(VARCHAR(16), M.dteKayitTarihi, 120) AS dteKayitTarihi, ISNULL(M.sKullaniciAdi, '') AS sKullaniciAdi, ISNULL(M.lTutarTL, 0) AS lTutarTL, diff --git a/svc/routes/production_product_costing.go b/svc/routes/production_product_costing.go index 4b84226..c5ab1f1 100644 --- a/svc/routes/production_product_costing.go +++ b/svc/routes/production_product_costing.go @@ -664,6 +664,7 @@ func GetProductionHasCostDetailHeaderHandler(w http.ResponseWriter, r *http.Requ &item.UrunAdi, &item.UretimSekliID, &item.UretimSekli, + &item.MaliyetTarihi, &item.DteKayitTarihi, &item.SKullaniciAdi, &item.LTutarTL, diff --git a/svc/routes/production_product_costing_pdf.go b/svc/routes/production_product_costing_pdf.go index 69fadbb..37fc075 100644 --- a/svc/routes/production_product_costing_pdf.go +++ b/svc/routes/production_product_costing_pdf.go @@ -6,7 +6,9 @@ import ( "database/sql" "fmt" "net/http" + "sort" "strings" + "time" "bssapp-backend/db" "bssapp-backend/models" @@ -56,6 +58,7 @@ func GetProductionProductCostingOnMLPDFHandler(w http.ResponseWriter, r *http.Re &header.UrunAdi, &header.UretimSekliID, &header.UretimSekli, + &header.MaliyetTarihi, &header.DteKayitTarihi, &header.SKullaniciAdi, &header.LTutarTL, @@ -245,7 +248,13 @@ func (c *costingPDF) drawHeaderFull() { pdf.SetFont("dejavu", "", 9) pdf.SetTextColor(60, 60, 60) - line1 := fmt.Sprintf("OnML No: %s | Tarih: %s | Uretim Sekli: %s", c.header.NOnMLNo, c.header.DteKayitTarihi, strings.TrimSpace(c.header.UretimSekli)) + line1 := fmt.Sprintf( + "OnML No: %s | Maliyet Tarihi: %s | Kayit Tarihi: %s | Uretim Sekli: %s", + c.header.NOnMLNo, + formatDateTRDot(c.header.MaliyetTarihi), + formatDateTRDot(c.header.DteKayitTarihi), + strings.TrimSpace(c.header.UretimSekli), + ) pdf.CellFormat(0, 5, line1, "", 1, "L", false, 0, "") line2 := fmt.Sprintf("Urun: %s - %s", strings.TrimSpace(c.header.UrunKodu), strings.TrimSpace(c.header.UrunAdi)) @@ -255,11 +264,20 @@ func (c *costingPDF) drawHeaderFull() { 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)) + line3 := fmt.Sprintf( + "Firma: %s | Kaydeden: %s | Son Guncelleme: %s (%s)", + firmaLabel, + strings.TrimSpace(c.header.SKullaniciAdi), + formatDateTRDot(c.header.DteGuncellemeTarihi), + strings.TrimSpace(c.header.SGuncellemeKullaniciAdi), + ) pdf.CellFormat(0, 5, line3, "", 1, "L", false, 0, "") pdf.SetTextColor(0, 0, 0) pdf.Ln(2) + + // Header summary tables (stacked) + c.drawHeaderSummaryTables() } func (c *costingPDF) drawHeaderCompact() { @@ -269,11 +287,202 @@ func (c *costingPDF) drawHeaderCompact() { 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) + title := fmt.Sprintf( + "OnML %s | %s - %s | %s | %s", + c.header.NOnMLNo, + strings.TrimSpace(c.header.UrunKodu), + strings.TrimSpace(c.header.UrunAdi), + formatDateTRDot(c.header.MaliyetTarihi), + firmaLabel, + ) pdf.CellFormat(0, 6, title, "", 1, "L", false, 0, "") pdf.Ln(1) } +type pdfPartSummaryRow struct { + name string + try float64 + usd float64 + eur float64 +} + +type pdfGroupTotalRow struct { + group string + try float64 + usd float64 +} + +func (c *costingPDF) drawHeaderSummaryTables() { + pdf := c.pdf + + partRows := c.computePartSummary() + groupRows, grandTRY, grandUSD := c.computeGroupTotals() + + // Table styling (use same brand palette as statements PDF) + // colorPrimary/colorSecondary/colorDetailFill are in statements_pdf.go (same package). + pdf.SetFont("dejavu", "B", 9.0) + pdf.SetTextColor(colorPrimary[0], colorPrimary[1], colorPrimary[2]) + pdf.CellFormat(0, 5.5, "Ozet", "", 1, "L", false, 0, "") + pdf.SetTextColor(0, 0, 0) + + // Part-based summary table + pdf.SetFont("dejavu", "B", 8.2) + pdf.CellFormat(0, 4.8, "Parca Bazli Maliyet Ozellikleri", "", 1, "L", false, 0, "") + partCols := []string{"Parca", "TRY", "USD", "EUR"} + partW := []float64{70, 22, 22, 22} + c.drawMiniTable(partCols, partW, func(i int) []string { + if i >= len(partRows) { + return nil + } + r := partRows[i] + return []string{r.name, pdfMoney(r.try), pdfMoney(r.usd), pdfMoney(r.eur)} + }, len(partRows), true) + pdf.Ln(2) + + // Group totals table + pdf.SetFont("dejavu", "B", 8.2) + pdf.CellFormat(0, 4.8, "Grup Toplamlari", "", 1, "L", false, 0, "") + gCols := []string{"Grup", "TRY", "USD"} + gW := []float64{30, 22, 22} + totalRows := append(groupRows, pdfGroupTotalRow{group: "TOPLAM", try: grandTRY, usd: grandUSD}) + c.drawMiniTable(gCols, gW, func(i int) []string { + if i >= len(totalRows) { + return nil + } + r := totalRows[i] + return []string{r.group, pdfMoney(r.try), pdfMoney(r.usd)} + }, len(totalRows), true) + pdf.Ln(2) +} + +func (c *costingPDF) computePartSummary() []pdfPartSummaryRow { + byName := map[string]*pdfPartSummaryRow{} + for _, g := range c.groups { + for _, it := range g.Items { + name := strings.TrimSpace(it.SParcaAdi) + if name == "" { + name = "-" + } + row := byName[name] + if row == nil { + row = &pdfPartSummaryRow{name: name} + byName[name] = row + } + row.try += it.LTutar + row.usd += it.USDTutar + row.eur += it.EURTutar + } + } + out := make([]pdfPartSummaryRow, 0, len(byName)) + for _, v := range byName { + out = append(out, *v) + } + // stable order + sort.Slice(out, func(i, j int) bool { return out[i].name < out[j].name }) + return out +} + +func (c *costingPDF) computeGroupTotals() (rows []pdfGroupTotalRow, grandTRY float64, grandUSD float64) { + want := []string{"CM2", "FABRIC", "DT", "TP"} + by := map[string]*pdfGroupTotalRow{} + for _, w := range want { + by[w] = &pdfGroupTotalRow{group: w} + } + + for _, g := range c.groups { + grp := strings.ToUpper(strings.TrimSpace(g.SAciklama3)) + t := by[grp] + if t == nil { + continue + } + // g.TotalTutar is TRY total; g.TotalUSDTutar is USD total + t.try += g.TotalTutar + t.usd += g.TotalUSDTutar + } + + for _, w := range want { + r := by[w] + rows = append(rows, *r) + grandTRY += r.try + grandUSD += r.usd + } + return rows, grandTRY, grandUSD +} + +func (c *costingPDF) drawMiniTable(cols []string, widths []float64, rowFn func(i int) []string, rowCount int, zebra bool) { + pdf := c.pdf + + // Header row + pdf.SetFont("dejavu", "B", 7.8) + pdf.SetFillColor(colorSecondary[0], colorSecondary[1], colorSecondary[2]) + pdf.SetDrawColor(160, 140, 100) + pdf.SetTextColor(0, 0, 0) + + x0 := pdf.GetX() + y0 := pdf.GetY() + x := x0 + hH := 5.4 + for i, col := range cols { + pdf.Rect(x, y0, widths[i], hH, "DF") + pdf.SetXY(x+0.8, y0+1.2) + pdf.CellFormat(widths[i]-1.6, hH-2.4, col, "", 0, "C", false, 0, "") + x += widths[i] + } + pdf.SetXY(x0, y0+hH) + + // Data rows + pdf.SetFont("dejavu", "", 7.4) + pdf.SetDrawColor(220, 220, 220) + for i := 0; i < rowCount; i++ { + row := rowFn(i) + if row == nil { + break + } + fill := zebra && (i%2 == 1) + if fill { + pdf.SetFillColor(colorDetailFill[0], colorDetailFill[1], colorDetailFill[2]) + } else { + pdf.SetFillColor(255, 255, 255) + } + x = x0 + y := pdf.GetY() + rh := 5.0 + for cidx, val := range row { + style := "" + if fill { + style = "DF" + } + pdf.Rect(x, y, widths[cidx], rh, style) + align := "L" + if cidx > 0 { + align = "R" + } + pdf.SetXY(x+0.8, y+(rh-3.5)/2) + pdf.CellFormat(widths[cidx]-1.6, 3.5, val, "", 0, align, false, 0, "") + x += widths[cidx] + } + pdf.SetXY(x0, y+rh) + } +} + +func formatDateTRDot(s string) string { + s = strings.TrimSpace(s) + if s == "" { + return "-" + } + layouts := []string{ + "2006-01-02 15:04:05", + "2006-01-02 15:04", + "2006-01-02", + } + for _, l := range layouts { + if t, err := time.ParseInLocation(l, s, time.Local); err == nil { + return t.Format("02.01.2006") + } + } + return s +} + func (c *costingPDF) drawGroup(g models.ProductionHasCostDetailGroup, firstGroup bool) { pdf := c.pdf @@ -300,8 +509,8 @@ func (c *costingPDF) drawGroup(g models.ProductionHasCostDetailGroup, firstGroup 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 { - c.drawRowWithGroup(it, wn, cols, g) + for i, it := range g.Items { + c.drawRowWithGroup(it, wn, cols, g, i) } pdf.Ln(2) _ = firstGroup @@ -310,18 +519,21 @@ func (c *costingPDF) drawGroup(g models.ProductionHasCostDetailGroup, firstGroup func (c *costingPDF) drawGroupBar(g models.ProductionHasCostDetailGroup, continued bool) { pdf := c.pdf pdf.SetFont("dejavu", "B", 10) - pdf.SetFillColor(245, 245, 245) + pdf.SetFillColor(colorSecondary[0], colorSecondary[1], colorSecondary[2]) + pdf.SetTextColor(0, 0, 0) name := strings.TrimSpace(g.SAciklama3) if continued { name = name + " (devam)" } pdf.CellFormat(0, 6, fmt.Sprintf("%s | Toplam TRY: %s | Toplam USD: %s", name, pdfMoney(g.TotalTutar), pdfMoney(g.TotalUSDTutar)), "1", 1, "L", true, 0, "") + pdf.SetTextColor(0, 0, 0) } func (c *costingPDF) drawTableHeader(cols []string, wn []float64) { pdf := c.pdf pdf.SetFont("dejavu", "B", 8) - pdf.SetFillColor(30, 30, 30) + pdf.SetFillColor(colorPrimary[0], colorPrimary[1], colorPrimary[2]) + pdf.SetDrawColor(120, 90, 20) pdf.SetTextColor(255, 255, 255) // Compute a stable header height based on wrapped labels. @@ -347,6 +559,7 @@ func (c *costingPDF) drawTableHeader(cols []string, wn []float64) { } pdf.SetXY(x0, y0+headerH) pdf.SetTextColor(0, 0, 0) + pdf.SetDrawColor(220, 220, 220) } func (c *costingPDF) ensureSpace(h float64) (newPage bool) { @@ -360,9 +573,10 @@ func (c *costingPDF) ensureSpace(h float64) (newPage bool) { return false } -func (c *costingPDF) drawRowWithGroup(it models.ProductionHasCostDetailGroupItem, wn []float64, cols []string, g models.ProductionHasCostDetailGroup) { +func (c *costingPDF) drawRowWithGroup(it models.ProductionHasCostDetailGroupItem, wn []float64, cols []string, g models.ProductionHasCostDetailGroup, rowIndex int) { pdf := c.pdf pdf.SetFont("dejavu", "", 7.2) + pdf.SetDrawColor(220, 220, 220) // Compute row height based on key wrapped cells. parcaLines := pdf.SplitLines([]byte(strings.TrimSpace(it.SParcaAdi)), wn[1]-2) @@ -398,32 +612,39 @@ func (c *costingPDF) drawRowWithGroup(it models.ProductionHasCostDetailGroupItem x0 := pdf.GetX() y0 := pdf.GetY() - c.drawCell(x0, y0, wn[0], rowH, it.NOnMLDetNo, "R") + fill := rowIndex%2 == 1 + if fill { + pdf.SetFillColor(colorDetailFill[0], colorDetailFill[1], colorDetailFill[2]) + } else { + pdf.SetFillColor(255, 255, 255) + } + + c.drawCell(x0, y0, wn[0], rowH, it.NOnMLDetNo, "R", fill) x := x0 + wn[0] - c.drawCellWrap(x, y0, wn[1], rowH, strings.TrimSpace(it.SParcaAdi), "L") + c.drawCellWrap(x, y0, wn[1], rowH, strings.TrimSpace(it.SParcaAdi), "L", fill) x += wn[1] - c.drawCellWrap(x, y0, wn[2], rowH, hLabel, "L") + c.drawCellWrap(x, y0, wn[2], rowH, hLabel, "L", fill) x += wn[2] - c.drawCellWrap(x, y0, wn[3], rowH, strings.TrimSpace(it.SKodu), "L") + c.drawCellWrap(x, y0, wn[3], rowH, strings.TrimSpace(it.SKodu), "L", fill) 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", fill) x += wn[4] - c.drawCell(x, y0, wn[5], rowH, strings.TrimSpace(it.SRenk), "L") + c.drawCell(x, y0, wn[5], rowH, strings.TrimSpace(it.SRenk), "L", fill) x += wn[5] - c.drawCell(x, y0, wn[6], rowH, pdfQty(it.LMiktar), "R") + c.drawCell(x, y0, wn[6], rowH, pdfQty(it.LMiktar), "R", fill) x += wn[6] - c.drawCell(x, y0, wn[7], rowH, strings.TrimSpace(it.SBirim), "C") + c.drawCell(x, y0, wn[7], rowH, strings.TrimSpace(it.SBirim), "C", fill) x += wn[7] // 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") + c.drawCell(x, y0, wn[8], rowH, pdfMoney(it.LDovizFiyati), "R", fill) x += wn[8] 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") + c.drawCell(x, y0, wn[9], rowH, pdfMoney(usdTotal), "R", fill) x += wn[9] // Prefer input price if present; otherwise lFiyat. @@ -431,9 +652,9 @@ func (c *costingPDF) drawRowWithGroup(it models.ProductionHasCostDetailGroupItem 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") + c.drawCell(x, y0, wn[10], rowH, pdfMoney(unitTRY), "R", fill) x += wn[10] - c.drawCell(x, y0, wn[11], rowH, pdfMoney(it.LTutar), "R") + c.drawCell(x, y0, wn[11], rowH, pdfMoney(it.LTutar), "R", fill) pdf.SetXY(x0, y0+rowH) } @@ -447,16 +668,24 @@ func (c *costingPDF) drawHeaderCellWrap(x, y, w, h float64, txt string) { 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, fill bool) { pdf := c.pdf - pdf.Rect(x, y, w, h, "") + style := "" + if fill { + style = "DF" + } + pdf.Rect(x, y, w, h, style) pdf.SetXY(x+0.8, y+(h-3.5)/2) pdf.CellFormat(w-1.6, 3.5, txt, "", 0, align, false, 0, "") } -func (c *costingPDF) drawCellWrap(x, y, w, h float64, txt, align string) { +func (c *costingPDF) drawCellWrap(x, y, w, h float64, txt, align string, fill bool) { pdf := c.pdf - pdf.Rect(x, y, w, h, "") + style := "" + if fill { + style = "DF" + } + pdf.Rect(x, y, w, h, style) pdf.SetXY(x+0.8, y+0.6) pdf.MultiCell(w-1.6, 3.5, txt, "", align, false) // restore cursor (MultiCell moves Y)