fix: adjust exported file column order and copywriting

This commit is contained in:
ValdiANS
2026-05-07 17:16:06 +07:00
parent fc5d5d8ad4
commit 4c6942c7b7
2 changed files with 177 additions and 43 deletions
@@ -50,6 +50,14 @@ func buildMarketingReportWorkbook(items []dto.RepportMarketingItemDTO) ([]byte,
if err := setMarketingReportRows(file, items); err != nil { if err := setMarketingReportRows(file, items); err != nil {
return nil, err return nil, err
} }
if err := file.SetPanes(marketingReportExportSheetName, &excelize.Panes{
Freeze: true,
YSplit: 1,
TopLeftCell: "A2",
ActivePane: "bottomLeft",
}); err != nil {
return nil, err
}
buffer, err := file.WriteToBuffer() buffer, err := file.WriteToBuffer()
if err != nil { if err != nil {
@@ -88,6 +96,10 @@ func setMarketingReportColumns(file *excelize.File) error {
} }
} }
if err := file.SetRowHeight(sheet, 1, 24); err != nil {
return err
}
return nil return nil
} }
@@ -110,7 +122,6 @@ func setMarketingReportHeaders(file *excelize.File) error {
"Bobot Total (Kg)", "Bobot Total (Kg)",
"Harga Jual (Rp)", "Harga Jual (Rp)",
"HPP (Rp)", "HPP (Rp)",
"HPP Amount (Rp)",
"Total (Rp)", "Total (Rp)",
} }
@@ -124,7 +135,22 @@ func setMarketingReportHeaders(file *excelize.File) error {
} }
} }
return nil headerStyle, err := file.NewStyle(&excelize.Style{
Font: &excelize.Font{Bold: true, Color: "1F2937"},
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"DCEBFA"}},
Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
Border: []excelize.Border{
{Type: "left", Color: "D1D5DB", Style: 1},
{Type: "top", Color: "D1D5DB", Style: 1},
{Type: "bottom", Color: "D1D5DB", Style: 1},
{Type: "right", Color: "D1D5DB", Style: 1},
},
})
if err != nil {
return err
}
return file.SetCellStyle(sheet, "A1", "Q1", headerStyle)
} }
func setMarketingReportRows(file *excelize.File, items []dto.RepportMarketingItemDTO) error { func setMarketingReportRows(file *excelize.File, items []dto.RepportMarketingItemDTO) error {
@@ -173,7 +199,6 @@ func setMarketingReportRows(file *excelize.File, items []dto.RepportMarketingIte
item.TotalWeightKg, item.TotalWeightKg,
formatMarketingRupiah(item.SalesPricePerKg), formatMarketingRupiah(item.SalesPricePerKg),
formatMarketingRupiah(item.HppPricePerKg), formatMarketingRupiah(item.HppPricePerKg),
formatMarketingRupiah(item.HppAmount),
formatMarketingRupiah(item.SalesAmount), formatMarketingRupiah(item.SalesAmount),
} }
@@ -210,15 +235,81 @@ func setMarketingReportRows(file *excelize.File, items []dto.RepportMarketingIte
if err := file.SetCellValue(sheet, "P"+totalRow, formatMarketingRupiah(summary.TotalHppPricePerKg)); err != nil { if err := file.SetCellValue(sheet, "P"+totalRow, formatMarketingRupiah(summary.TotalHppPricePerKg)); err != nil {
return err return err
} }
if err := file.SetCellValue(sheet, "Q"+totalRow, formatMarketingRupiah(float64(summary.TotalHppAmount))); err != nil { if err := file.SetCellValue(sheet, "Q"+totalRow, formatMarketingRupiah(float64(summary.TotalSalesAmount))); err != nil {
return err
}
if err := file.SetCellValue(sheet, "R"+totalRow, formatMarketingRupiah(float64(summary.TotalSalesAmount))); err != nil {
return err return err
} }
} }
return nil if len(items) > 0 {
lastDataRow := strconv.Itoa(len(items) + 1)
dataStyle, err := file.NewStyle(&excelize.Style{
Alignment: &excelize.Alignment{Horizontal: "left", Vertical: "center", WrapText: true},
Border: []excelize.Border{
{Type: "left", Color: "D1D5DB", Style: 1},
{Type: "top", Color: "D1D5DB", Style: 1},
{Type: "bottom", Color: "D1D5DB", Style: 1},
{Type: "right", Color: "D1D5DB", Style: 1},
},
})
if err != nil {
return err
}
if err := file.SetCellStyle(sheet, "A2", "Q"+lastDataRow, dataStyle); err != nil {
return err
}
numericStyle, err := file.NewStyle(&excelize.Style{
Alignment: &excelize.Alignment{Horizontal: "right", Vertical: "center"},
Border: []excelize.Border{
{Type: "left", Color: "D1D5DB", Style: 1},
{Type: "top", Color: "D1D5DB", Style: 1},
{Type: "bottom", Color: "D1D5DB", Style: 1},
{Type: "right", Color: "D1D5DB", Style: 1},
},
})
if err != nil {
return err
}
if err := file.SetCellStyle(sheet, "L2", "Q"+lastDataRow, numericStyle); err != nil {
return err
}
}
totalTextStyle, err := file.NewStyle(&excelize.Style{
Font: &excelize.Font{Bold: true, Color: "1F2937"},
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"F3F4F6"}},
Alignment: &excelize.Alignment{Horizontal: "left", Vertical: "center"},
Border: []excelize.Border{
{Type: "left", Color: "D1D5DB", Style: 1},
{Type: "top", Color: "D1D5DB", Style: 1},
{Type: "bottom", Color: "D1D5DB", Style: 1},
{Type: "right", Color: "D1D5DB", Style: 1},
},
})
if err != nil {
return err
}
if err := file.SetCellStyle(sheet, "A"+totalRow, "Q"+totalRow, totalTextStyle); err != nil {
return err
}
totalNumericStyle, err := file.NewStyle(&excelize.Style{
Font: &excelize.Font{Bold: true, Color: "1F2937"},
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"F3F4F6"}},
Alignment: &excelize.Alignment{Horizontal: "right", Vertical: "center"},
Border: []excelize.Border{
{Type: "left", Color: "D1D5DB", Style: 1},
{Type: "top", Color: "D1D5DB", Style: 1},
{Type: "bottom", Color: "D1D5DB", Style: 1},
{Type: "right", Color: "D1D5DB", Style: 1},
},
})
if err != nil {
return err
}
return file.SetCellStyle(sheet, "L"+totalRow, "Q"+totalRow, totalNumericStyle)
} }
func formatMarketingDate(t time.Time) string { func formatMarketingDate(t time.Time) string {
@@ -56,22 +56,21 @@ type pdfColumn struct {
var marketingPdfColumns = []pdfColumn{ var marketingPdfColumns = []pdfColumn{
{"No", 6, "C"}, {"No", 6, "C"},
{"Tanggal\nJual", 16, "C"}, {"Tanggal\nJual", 16, "C"},
{"Tanggal\nRealisasi", 16, "C"}, {"Tanggal\nRealisasi", 20, "C"},
{"Aging\n(Hari)", 9, "C"}, {"Aging\n(Hari)", 9, "C"},
{"Gudang\nFisik", 20, "L"}, {"Gudang\nFisik", 20, "L"},
{"Pelanggan", 20, "L"}, {"Pelanggan", 20, "L"},
{"No. DO", 18, "L"}, {"No. DO", 18, "L"},
{"Sales", 18, "L"}, {"Sales", 18, "L"},
{"No. Polisi", 18, "L"}, {"No. Polisi", 18, "L"},
{"Tipe\nMarketing", 14, "C"}, {"Tipe\nMarketing", 16, "C"},
{"Produk", 16, "L"}, {"Produk", 16, "L"},
{"Kuantitas", 13, "R"}, {"Kuantitas", 13, "R"},
{"Bobot Rata-Rata\n(Kg)", 13, "R"}, {"Bobot\nRata-Rata (Kg)", 18, "R"},
{"Bobot Total Berat\n(Kg)", 14, "R"}, {"Bobot\nTotal Berat (Kg)", 18, "R"},
{"Harga Jual\n(Rp)", 17, "R"}, {"Harga Jual\n(Rp)", 17, "R"},
{"HPP\n(Rp)", 17, "R"}, {"HPP\n(Rp)", 17, "R"},
{"Total Jual\n(Rp)", 18, "R"}, {"Total (Rp)", 18, "R"},
{"Total HPP\n(Rp)", 18, "R"},
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -214,7 +213,7 @@ func calcMarketingRowHeight(pdf *fpdf.Fpdf, values []string, lineH float64) floa
cols := marketingPdfColumns cols := marketingPdfColumns
maxLines := 1 maxLines := 1
for i, col := range cols { for i, col := range cols {
if i >= len(values) || i == 10 { if i >= len(values) || i == 9 {
continue continue
} }
usableW := col.width - 2*margin usableW := col.width - 2*margin
@@ -238,7 +237,7 @@ func writeMarketingPdfRow(pdf *fpdf.Fpdf, item dto.RepportMarketingItemDTO, valu
x := 10.0 x := 10.0
for i, col := range cols { for i, col := range cols {
if i == 10 { if i == 9 {
drawMarketingTypeBadge(pdf, x, y, col.width, rowH, item.MarketingType) drawMarketingTypeBadge(pdf, x, y, col.width, rowH, item.MarketingType)
pdf.SetDrawColor(borderR, borderG, borderB) pdf.SetDrawColor(borderR, borderG, borderB)
pdf.SetTextColor(40, 40, 40) pdf.SetTextColor(40, 40, 40)
@@ -283,8 +282,8 @@ func marketingPdfRowValues(no int, item dto.RepportMarketingItemDTO) []string {
customer, customer,
safeMarketingExportText(item.DoNumber), safeMarketingExportText(item.DoNumber),
sales, sales,
safeMarketingExportText(item.VehicleNumber), safeMarketingExportText(formatMarketingVehicleNumber(item.VehicleNumber)),
safeMarketingExportText(item.MarketingType), // index 10, overridden by badge safeMarketingExportText(item.MarketingType), // index 9, overridden by badge
product, product,
formatMarketingPdfNumber(item.Qty), formatMarketingPdfNumber(item.Qty),
formatMarketingPdfDecimal(item.AverageWeightKg), formatMarketingPdfDecimal(item.AverageWeightKg),
@@ -292,7 +291,6 @@ func marketingPdfRowValues(no int, item dto.RepportMarketingItemDTO) []string {
formatMarketingPdfRupiah(item.SalesPricePerKg), formatMarketingPdfRupiah(item.SalesPricePerKg),
formatMarketingPdfRupiah(item.HppPricePerKg), formatMarketingPdfRupiah(item.HppPricePerKg),
formatMarketingPdfRupiah(item.SalesAmount), formatMarketingPdfRupiah(item.SalesAmount),
formatMarketingPdfRupiah(item.HppAmount),
} }
} }
@@ -306,30 +304,9 @@ func writeMarketingPdfTotal(pdf *fpdf.Fpdf, items []dto.RepportMarketingItemDTO)
return return
} }
rowH := 6.5
if pdf.GetY()+rowH > marketingPdfPageHeight(pdf)-12 {
pdf.AddPage()
writeMarketingPdfHeader(pdf)
}
pdf.SetFont("Helvetica", "B", 6) pdf.SetFont("Helvetica", "B", 6)
pdf.SetFillColor(220, 230, 245)
pdf.SetTextColor(30, 64, 120)
pdf.SetDrawColor(borderR, borderG, borderB)
pdf.SetLineWidth(0.1)
y := pdf.GetY() lineH := 5.0
x := 10.0
// merge first 11 cols (No … Tipe Marketing) into "TOTAL" label
mergedWidth := 0.0
for i := 0; i < 11; i++ {
mergedWidth += marketingPdfColumns[i].width
}
pdf.SetXY(x, y)
pdf.CellFormat(mergedWidth, rowH, "TOTAL", "1", 0, "R", true, 0, "")
x += mergedWidth
totals := []string{ totals := []string{
formatMarketingPdfNumber(float64(summary.TotalQty)), formatMarketingPdfNumber(float64(summary.TotalQty)),
@@ -338,13 +315,58 @@ func writeMarketingPdfTotal(pdf *fpdf.Fpdf, items []dto.RepportMarketingItemDTO)
formatMarketingPdfRupiah(summary.AverageSalesPrice), formatMarketingPdfRupiah(summary.AverageSalesPrice),
formatMarketingPdfRupiah(summary.TotalHppPricePerKg), formatMarketingPdfRupiah(summary.TotalHppPricePerKg),
formatMarketingPdfRupiah(float64(summary.TotalSalesAmount)), formatMarketingPdfRupiah(float64(summary.TotalSalesAmount)),
formatMarketingPdfRupiah(float64(summary.TotalHppAmount)),
} }
margin := pdf.GetCellMargin()
maxLines := 1
for i, val := range totals {
col := marketingPdfColumns[11+i]
usableW := col.width - 2*margin
if usableW <= 0 {
continue
}
lines := pdf.SplitLines([]byte(val), usableW)
n := len(lines)
if n == 0 {
n = 1
}
if n > maxLines {
maxLines = n
}
}
rowH := float64(maxLines) * lineH
if pdf.GetY()+rowH > marketingPdfPageHeight(pdf)-12 {
pdf.AddPage()
writeMarketingPdfHeader(pdf)
pdf.SetFont("Helvetica", "B", 6)
}
pdf.SetTextColor(30, 64, 120)
pdf.SetDrawColor(borderR, borderG, borderB)
pdf.SetLineWidth(0.1)
y := pdf.GetY()
x := 10.0
const totalFillR, totalFillG, totalFillB = 220, 230, 245
mergedWidth := 0.0
for i := range 11 {
mergedWidth += marketingPdfColumns[i].width
}
pdf.SetFillColor(totalFillR, totalFillG, totalFillB)
pdf.Rect(x, y, mergedWidth, rowH, "FD")
pdf.SetXY(x, y)
pdf.MultiCell(mergedWidth, lineH, "TOTAL", "", "R", false)
x += mergedWidth
for i, val := range totals { for i, val := range totals {
col := marketingPdfColumns[11+i] col := marketingPdfColumns[11+i]
pdf.SetFillColor(totalFillR, totalFillG, totalFillB)
pdf.Rect(x, y, col.width, rowH, "FD")
pdf.SetXY(x, y) pdf.SetXY(x, y)
pdf.CellFormat(col.width, rowH, val, "1", 0, "R", true, 0, "") pdf.MultiCell(col.width, lineH, val, "", "R", false)
x += col.width x += col.width
} }
@@ -510,6 +532,27 @@ func marketingPdfPageHeight(pdf *fpdf.Fpdf) float64 {
return h return h
} }
// formatMarketingVehicleNumber spaces out Indonesian plate segments: D1234MBU → D 1234 MBU.
// Returns s unchanged if it doesn't match the [letters][digits][letters] pattern.
func formatMarketingVehicleNumber(s string) string {
s = strings.TrimSpace(s)
if s == "" {
return s
}
i := 0
for i < len(s) && (s[i] >= 'A' && s[i] <= 'Z' || s[i] >= 'a' && s[i] <= 'z') {
i++
}
j := i
for j < len(s) && s[j] >= '0' && s[j] <= '9' {
j++
}
if i == 0 || j == i || j == len(s) {
return s
}
return s[:i] + " " + s[i:j] + " " + s[j:]
}
// formatMarketingPdfThousands inserts period every 3 digits. // formatMarketingPdfThousands inserts period every 3 digits.
func formatMarketingPdfThousands(v int64) string { func formatMarketingPdfThousands(v int64) string {
negative := v < 0 negative := v < 0