Files
bssapp/svc/routes/production_product_costing_pdf.go
2026-05-22 14:57:56 +03:00

763 lines
20 KiB
Go

package routes
import (
"bytes"
"context"
"database/sql"
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"time"
"bssapp-backend/db"
"bssapp-backend/models"
"bssapp-backend/queries"
"bssapp-backend/utils"
"github.com/jung-kurt/gofpdf"
)
// GET /api/pricing/production-product-costing/onml/pdf?n_onml_no=100001
// Generates a PDF export for the costing detail screen (has-cost).
func GetProductionProductCostingOnMLPDFHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/pdf")
uretimDB := db.GetUretimDB()
if uretimDB == nil {
http.Error(w, "URETIM veritabani baglantisi aktif degil", http.StatusServiceUnavailable)
return
}
nOnMLNo := parsePositiveIntOrDefault(r.URL.Query().Get("n_onml_no"), 0)
if nOnMLNo <= 0 {
http.Error(w, "n_onml_no zorunlu", http.StatusBadRequest)
return
}
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
logger := utils.SlogFromContext(ctx).With("handler", "production-product-costing.onml.pdf", "n_onml_no", nOnMLNo)
logger.Info("request start")
// Header
hRow, err := queries.GetProductionHasCostDetailHeaderByOnMLNo(ctx, uretimDB, nOnMLNo)
if err != nil {
logger.Error("header query prepare error", "err", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
var header models.ProductionHasCostDetailHeader
if err := hRow.Scan(
&header.UretimiYapanFirma,
&header.SonIsEmriVeren,
&header.FirmaKodu,
&header.NFirmaID,
&header.NOnMLNo,
&header.UrunKodu,
&header.UrunAdi,
&header.UretimSekliID,
&header.UretimSekli,
&header.MaliyetTarihi,
&header.DteKayitTarihi,
&header.SKullaniciAdi,
&header.LTutarTL,
&header.LTutarUSD,
&header.LTutarEURO,
&header.LTutarGBP,
&header.SDovizCinsi,
&header.LTutarDoviz,
&header.DteGuncellemeTarihi,
&header.SGuncellemeKullaniciAdi,
&header.NUrtReceteID,
); err != nil {
if err == sql.ErrNoRows {
http.Error(w, "Kayit bulunamadi", http.StatusNotFound)
return
}
logger.Error("header scan error", "err", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
// Detail groups
groups, err := loadHasCostDetailGroups(ctx, uretimDB, nOnMLNo)
if err != nil {
logger.Error("groups load error", "err", err)
http.Error(w, "Veritabani hatasi", http.StatusInternalServerError)
return
}
// Exchange rates (needed for EUR conversions in PDF summary tables)
usdRate := 0.0
eurRate := 0.0
if mssqlDB := db.GetDB(); mssqlDB != nil {
row, err := queries.GetProductionHasCostDetailExchangeRatesByDate(ctx, mssqlDB, header.MaliyetTarihi)
if err == nil {
var rateDate string
var gbpRate float64
_ = row.Scan(&rateDate, &usdRate, &eurRate, &gbpRate)
}
}
pdf := gofpdf.New("L", "mm", "A4", "")
pdf.SetMargins(8, 8, 8)
pdf.SetAutoPageBreak(false, 10)
if err := registerDejavuFonts(pdf, "dejavu"); err != nil {
http.Error(w, "pdf font error: "+err.Error(), http.StatusInternalServerError)
return
}
export := &costingPDF{
pdf: pdf,
header: header,
groups: groups,
usdRate: usdRate,
eurRate: eurRate,
}
export.draw()
if err := pdf.Error(); err != nil {
http.Error(w, "pdf render error: "+err.Error(), http.StatusInternalServerError)
return
}
var buf bytes.Buffer
if err := pdf.Output(&buf); err != nil {
http.Error(w, "pdf output error: "+err.Error(), http.StatusInternalServerError)
return
}
filename := fmt.Sprintf("onml-%d.pdf", nOnMLNo)
w.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, filename))
w.WriteHeader(http.StatusOK)
_, _ = w.Write(buf.Bytes())
logger.Info("request done")
}
func loadHasCostDetailGroups(ctx context.Context, uretimDB *sql.DB, nOnMLNo int) ([]models.ProductionHasCostDetailGroup, error) {
rows, err := queries.GetProductionHasCostDetailRowsByOnMLNo(ctx, uretimDB, nOnMLNo)
if err != nil {
return nil, err
}
defer rows.Close()
groups := make([]models.ProductionHasCostDetailGroup, 0, 16)
groupIndexByName := map[string]int{}
for rows.Next() {
var (
groupName string
groupTotal float64
groupTotalUSD float64
nOnMLNoStr string
nOnMLDetNoStr string
hNoStr string
mtBolumStr string
fiyatGirilen sql.NullFloat64
fiyatDoviz sql.NullString
maliyeteDahil sql.NullBool
cmPriceTypeID sql.NullInt64
item models.ProductionHasCostDetailGroupItem
)
if err := rows.Scan(
&groupName,
&groupTotal,
&groupTotalUSD,
&nOnMLNoStr,
&nOnMLDetNoStr,
&hNoStr,
&mtBolumStr,
&item.SKodu,
&item.SAciklama,
&item.SRenk,
&item.SBeden,
&item.SAciklama2,
&item.LMiktar,
&item.LFiyat,
&item.LTutar,
&item.SFiyatTipi,
&item.SDovizCinsi,
&item.LDovizKuru,
&item.LDovizFiyati,
&fiyatGirilen,
&fiyatDoviz,
&maliyeteDahil,
&cmPriceTypeID,
&item.USDTutar,
&item.EURTutar,
&item.GBPTutar,
&item.SBirim,
&item.SHammaddeTuruAdi,
&item.SParcaAdi,
); err != nil {
continue
}
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
}
if fiyatDoviz.Valid {
item.FiyatDoviz = strings.TrimSpace(fiyatDoviz.String)
}
item.MaliyeteDahil = maliyeteDahil.Valid && maliyeteDahil.Bool
if cmPriceTypeID.Valid {
v := int(cmPriceTypeID.Int64)
item.CMPriceTypeID = &v
}
idx, ok := groupIndexByName[groupName]
if !ok {
groups = append(groups, models.ProductionHasCostDetailGroup{
SAciklama3: groupName,
TotalTutar: groupTotal,
TotalUSDTutar: groupTotalUSD,
Items: make([]models.ProductionHasCostDetailGroupItem, 0, 24),
})
idx = len(groups) - 1
groupIndexByName[groupName] = idx
}
groups[idx].Items = append(groups[idx].Items, item)
}
if err := rows.Err(); err != nil {
return nil, err
}
return groups, nil
}
// --- PDF drawing ---
type costingPDF struct {
pdf *gofpdf.Fpdf
header models.ProductionHasCostDetailHeader
groups []models.ProductionHasCostDetailGroup
usdRate float64
eurRate float64
}
func (c *costingPDF) draw() {
c.addPage(true)
for gi, g := range c.groups {
c.drawGroup(g, gi == 0)
}
}
func (c *costingPDF) addPage(fullHeader bool) {
c.pdf.AddPage()
if fullHeader {
c.drawHeaderFull()
} else {
c.drawHeaderCompact()
}
}
func (c *costingPDF) drawHeaderFull() {
pdf := c.pdf
pdf.SetFont("dejavu", "B", 14)
pdf.CellFormat(0, 7, "Maliyet Detay", "", 1, "L", false, 0, "")
pdf.SetFont("dejavu", "", 9)
pdf.SetTextColor(60, 60, 60)
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))
pdf.CellFormat(0, 5, line2, "", 1, "L", false, 0, "")
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 | 2.Firma: %s | Kaydeden: %s | Son Guncelleme: %s (%s)",
firmaLabel,
strings.TrimSpace(c.header.SonIsEmriVeren),
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() {
pdf := c.pdf
pdf.SetFont("dejavu", "B", 10.5)
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),
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
eur float64
}
func (c *costingPDF) drawHeaderSummaryTables() {
pdf := c.pdf
groupRows, grandTRY, grandUSD, grandEUR := 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)
// 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", "EUR"}
gW := []float64{30, 22, 22, 22}
totalRows := append(groupRows, pdfGroupTotalRow{group: "TOPLAM", try: grandTRY, usd: grandUSD, eur: grandEUR})
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), pdfMoney(r.eur)}
}, len(totalRows), true, 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
// EUR isn't directly returned by the has-cost query; derive from USD using exchange rates when available.
if c.usdRate > 0 && c.eurRate > 0 {
row.eur += it.USDTutar * (c.usdRate / c.eurRate)
}
}
}
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, grandEUR 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
if c.usdRate > 0 && c.eurRate > 0 {
t.eur += g.TotalUSDTutar * (c.usdRate / c.eurRate)
}
}
for _, w := range want {
r := by[w]
rows = append(rows, *r)
grandTRY += r.try
grandUSD += r.usd
grandEUR += r.eur
}
return rows, grandTRY, grandUSD, grandEUR
}
func (c *costingPDF) drawMiniTable(cols []string, widths []float64, rowFn func(i int) []string, rowCount int, zebra bool, emphasizeLastRow 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.SetDrawColor(220, 220, 220)
for i := 0; i < rowCount; i++ {
row := rowFn(i)
if row == nil {
break
}
isLast := emphasizeLastRow && (i == rowCount-1)
fill := zebra && (i%2 == 1)
// Style rules:
// - Total row: primary fill + bigger bold text
// - Zebra rows: detail fill
// - Normal: white
if isLast {
pdf.SetFont("dejavu", "B", 8.6)
pdf.SetTextColor(255, 255, 255)
pdf.SetFillColor(colorPrimary[0], colorPrimary[1], colorPrimary[2])
} else {
pdf.SetFont("dejavu", "", 7.4)
pdf.SetTextColor(30, 30, 30)
if fill {
pdf.SetFillColor(colorDetailFill[0], colorDetailFill[1], colorDetailFill[2])
} else {
pdf.SetFillColor(255, 255, 255)
}
}
x = x0
y := pdf.GetY()
rh := 5.0
if isLast {
rh = 5.6
}
for cidx, val := range row {
style := ""
if fill || isLast {
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)
}
pdf.SetTextColor(0, 0, 0)
pdf.SetFont("dejavu", "", 7.4)
}
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
// Reset any font/color left over from header summary tables.
pdf.SetFont("dejavu", "", 7.2)
pdf.SetTextColor(0, 0, 0)
// Group bar
c.drawGroupBar(g, false)
// Columns
// 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",
"Br\nFiyat",
"Pr\nBr",
"USD\nFiyat",
"USD\nTutar",
"TRY\nFiyat",
"TRY\nTutar",
}
wn := []float64{8, 20, 22, 30, 56, 14, 14, 10, 16, 12, 16, 16, 16, 16} // sum = 252
c.drawTableHeader(cols, wn)
// PDF-specific ordering: by hammadde turu no, then code.
items := append([]models.ProductionHasCostDetailGroupItem(nil), g.Items...)
sort.Slice(items, func(i, j int) bool {
ai, _ := strconv.Atoi(strings.TrimSpace(items[i].NHammaddeTuruNo))
aj, _ := strconv.Atoi(strings.TrimSpace(items[j].NHammaddeTuruNo))
if ai != aj {
return ai < aj
}
return strings.TrimSpace(items[i].SKodu) < strings.TrimSpace(items[j].SKodu)
})
for i, it := range items {
c.drawRowWithGroup(it, wn, cols, g, i)
}
pdf.Ln(2)
_ = firstGroup
}
func (c *costingPDF) drawGroupBar(g models.ProductionHasCostDetailGroup, continued bool) {
pdf := c.pdf
pdf.SetFont("dejavu", "B", 10)
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(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.
maxLines := 1
for i, col := range cols {
lines := pdf.SplitLines([]byte(col), wn[i]-1.6)
if len(lines) > maxLines {
maxLines = len(lines)
}
}
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.SetDrawColor(220, 220, 220)
}
func (c *costingPDF) ensureSpace(h float64) (newPage bool) {
pdf := c.pdf
_, pageH := pdf.GetPageSize()
_, _, _, mb := pdf.GetMargins()
if pdf.GetY()+h > pageH-mb {
c.addPage(false)
return true
}
return false
}
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)
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)
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
}
if c.ensureSpace(rowH + 12.0) {
// Redraw group bar + table header on new page.
c.drawGroupBar(g, true)
c.drawTableHeader(cols, wn)
}
x0 := pdf.GetX()
y0 := pdf.GetY()
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", fill)
x += wn[1]
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", fill)
x += wn[3]
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", fill)
x += wn[5]
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", fill)
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", fill)
x += wn[8]
c.drawCell(x, y0, wn[9], rowH, cur, "C", fill)
x += wn[9]
// Always show USD/TRY unit+total.
// In URETIM schema: lFiyat/lTutar are in TRY, lDovizFiyati/usdTutar are in USD.
c.drawCell(x, y0, wn[10], rowH, pdfMoney(it.LDovizFiyati), "R", fill)
x += wn[10]
usdTotal := it.USDTutar
if usdTotal == 0 && it.LMiktar != 0 && it.LDovizFiyati != 0 {
usdTotal = it.LMiktar * it.LDovizFiyati
}
c.drawCell(x, y0, wn[11], rowH, pdfMoney(usdTotal), "R", fill)
x += wn[11]
// 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[12], rowH, pdfMoney(unitTRY), "R", fill)
x += wn[12]
c.drawCell(x, y0, wn[13], rowH, pdfMoney(it.LTutar), "R", fill)
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, fill bool) {
pdf := c.pdf
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, fill bool) {
pdf := c.pdf
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)
pdf.SetXY(x+w, y)
}
func pdfMoney(v float64) string {
// 2 decimals, dot
return fmt.Sprintf("%.2f", v)
}
func pdfQty(v float64) string {
// 4 decimals (screen-like)
return fmt.Sprintf("%.4f", v)
}