134 lines
3.4 KiB
Go
134 lines
3.4 KiB
Go
package service
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"html/template"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
)
|
|
|
|
func renderTemplateToPDF(templatePath string, data interface{}) ([]byte, error) {
|
|
// Create template with custom functions
|
|
funcMap := template.FuncMap{
|
|
"add": func(a, b int) int {
|
|
return a + b
|
|
},
|
|
}
|
|
|
|
// Parse and execute HTML template
|
|
tmpl, err := template.New(filepath.Base(templatePath)).Funcs(funcMap).ParseFiles(templatePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse template: %w", err)
|
|
}
|
|
var htmlBuf bytes.Buffer
|
|
if err := tmpl.Execute(&htmlBuf, data); err != nil {
|
|
return nil, fmt.Errorf("execute template: %w", err)
|
|
}
|
|
|
|
// Write HTML to a temp file
|
|
tmpDir, err := os.MkdirTemp("", "daily_report_")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("tmp dir: %w", err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
htmlPath := filepath.Join(tmpDir, "report.html")
|
|
pdfPath := filepath.Join(tmpDir, "report.pdf")
|
|
|
|
if err := os.WriteFile(htmlPath, htmlBuf.Bytes(), 0644); err != nil {
|
|
return nil, fmt.Errorf("write html: %w", err)
|
|
}
|
|
|
|
// Use Chrome headless for better CSS rendering
|
|
chromeArgs := []string{
|
|
"--headless",
|
|
"--disable-gpu",
|
|
"--no-sandbox",
|
|
"--disable-dev-shm-usage",
|
|
"--print-to-pdf=" + pdfPath,
|
|
"--print-to-pdf-no-header",
|
|
"--print-to-pdf-no-footer",
|
|
"--print-to-pdf-margin-top=0",
|
|
"--print-to-pdf-margin-bottom=0",
|
|
"--print-to-pdf-margin-left=0",
|
|
"--print-to-pdf-margin-right=0",
|
|
"--print-to-pdf-paper-width=210mm",
|
|
"--print-to-pdf-paper-height=297mm",
|
|
htmlPath,
|
|
}
|
|
|
|
// Try Google Chrome first, then Chromium, then wkhtmltopdf as fallback
|
|
chromeCmd := exec.Command("google-chrome", chromeArgs...)
|
|
if out, err := chromeCmd.CombinedOutput(); err != nil {
|
|
// Fallback to Chromium
|
|
chromiumCmd := exec.Command("chromium", chromeArgs...)
|
|
if chromiumOut, err := chromiumCmd.CombinedOutput(); err != nil {
|
|
// Final fallback to wkhtmltopdf
|
|
wkhtmlArgs := []string{
|
|
"--enable-local-file-access",
|
|
"--dpi", "300",
|
|
"--page-size", "A4",
|
|
"--orientation", "Portrait",
|
|
"--margin-top", "0",
|
|
"--margin-bottom", "0",
|
|
"--margin-left", "0",
|
|
"--margin-right", "0",
|
|
htmlPath,
|
|
pdfPath,
|
|
}
|
|
wkhtmlCmd := exec.Command("wkhtmltopdf", wkhtmlArgs...)
|
|
if wkhtmlOut, err := wkhtmlCmd.CombinedOutput(); err != nil {
|
|
return nil, fmt.Errorf("all PDF generators failed. Chrome error: %v, Chromium error: %v, wkhtmltopdf error: %v, chrome output: %s, chromium output: %s, wkhtmltopdf output: %s", chromeCmd.Err, chromiumCmd.Err, err, string(out), string(chromiumOut), string(wkhtmlOut))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read PDF bytes
|
|
pdfBytes, err := os.ReadFile(pdfPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read pdf: %w", err)
|
|
}
|
|
return pdfBytes, nil
|
|
}
|
|
|
|
func formatCurrency(amount float64) string {
|
|
// Simple currency formatting: Rp with thousands separators
|
|
// Note: For exact locale formatting, integrate a locale library later
|
|
s := fmt.Sprintf("%.0f", amount) // Remove decimal places for cleaner display
|
|
intPart := addThousandsSep(s)
|
|
return "Rp " + intPart
|
|
}
|
|
|
|
func splitAmount(s string) (string, string) {
|
|
for i := len(s) - 1; i >= 0; i-- {
|
|
if s[i] == '.' {
|
|
return s[:i], s[i+1:]
|
|
}
|
|
}
|
|
return s, "00"
|
|
}
|
|
|
|
func addThousandsSep(s string) string {
|
|
n := len(s)
|
|
if n <= 3 {
|
|
return s
|
|
}
|
|
var out bytes.Buffer
|
|
pre := n % 3
|
|
if pre > 0 {
|
|
out.WriteString(s[:pre])
|
|
if n > pre {
|
|
out.WriteByte('.')
|
|
}
|
|
}
|
|
for i := pre; i < n; i += 3 {
|
|
out.WriteString(s[i : i+3])
|
|
if i+3 < n {
|
|
out.WriteByte('.')
|
|
}
|
|
}
|
|
return out.String()
|
|
}
|