package controller import ( "fmt" "strconv" "strings" "time" "github.com/gofiber/fiber/v2" "github.com/xuri/excelize/v2" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" ) const transactionExportSheetName = "Transaksi" func exportTransactionListExcel(c *fiber.Ctx, payments []entity.Payment) error { content, err := buildTransactionExportWorkbook(payments) if err != nil { return fiber.NewError(fiber.StatusInternalServerError, "failed to generate excel file") } filename := fmt.Sprintf("transaksi_%s.xlsx", time.Now().Format("20060102_150405")) c.Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename)) return c.Status(fiber.StatusOK).Send(content) } func buildTransactionExportWorkbook(payments []entity.Payment) ([]byte, error) { file := excelize.NewFile() defer file.Close() defaultSheet := file.GetSheetName(file.GetActiveSheetIndex()) if defaultSheet != transactionExportSheetName { if err := file.SetSheetName(defaultSheet, transactionExportSheetName); err != nil { return nil, err } } if err := setTransactionExportColumns(file); err != nil { return nil, err } if err := setTransactionExportHeaders(file); err != nil { return nil, err } if err := setTransactionExportRows(file, payments); err != nil { return nil, err } if err := file.SetPanes(transactionExportSheetName, &excelize.Panes{ Freeze: true, YSplit: 1, TopLeftCell: "A2", ActivePane: "bottomLeft", }); err != nil { return nil, err } buffer, err := file.WriteToBuffer() if err != nil { return nil, err } return buffer.Bytes(), nil } func setTransactionExportColumns(file *excelize.File) error { columnWidths := map[string]float64{ "A": 20, "B": 22, "C": 18, "D": 25, "E": 14, "F": 16, "G": 16, "H": 22, "I": 22, "J": 18, "K": 18, "L": 18, "M": 30, "N": 22, "O": 20, } sheet := transactionExportSheetName for col, width := range columnWidths { if err := file.SetColWidth(sheet, col, col, width); err != nil { return err } } return file.SetRowHeight(sheet, 1, 24) } func setTransactionExportHeaders(file *excelize.File) error { sheet := transactionExportSheetName headers := []string{ "Kode Pembayaran", "No. Referensi", "Tipe Transaksi", "Pihak", "Tipe Pihak", "Tanggal Bayar", "Metode Bayar", "Bank", "No. Rekening Bank", "Pemasukan", "Pengeluaran", "Nominal", "Catatan", "Dibuat Oleh", "Status", } for i, header := range headers { colName, err := excelize.ColumnNumberToName(i + 1) if err != nil { return err } if err := file.SetCellValue(sheet, colName+"1", header); err != nil { return err } } 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", "O1", headerStyle) } func setTransactionExportRows(file *excelize.File, payments []entity.Payment) error { if len(payments) == 0 { return nil } sheet := transactionExportSheetName for i, p := range payments { row := strconv.Itoa(i + 2) if err := writeTransactionExportRow(file, sheet, row, p); err != nil { return err } } lastRow := strconv.Itoa(len(payments) + 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", "O"+lastRow, 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 } return file.SetCellStyle(sheet, "J2", "L"+lastRow, numericStyle) } func writeTransactionExportRow(file *excelize.File, sheet, row string, p entity.Payment) error { incomeAmount, expenseAmount := txAmounts(p.Direction, p.Nominal) values := []interface{}{ safeTxText(p.PaymentCode), safeTxRefNumber(p.ReferenceNumber), safeTxText(txTransactionType(p)), safeTxText(txPartyName(p)), safeTxText(p.PartyType), formatTxDate(p.PaymentDate), safeTxText(p.PaymentMethod), safeTxBank(p), safeTxBankAccount(p), incomeAmount, expenseAmount, p.Nominal, safeTxText(p.Notes), safeTxText(txCreatedBy(p)), formatTxStatus(p), } for colIdx, val := range values { colName, err := excelize.ColumnNumberToName(colIdx + 1) if err != nil { return err } if err := file.SetCellValue(sheet, colName+row, val); err != nil { return err } } return nil } func safeTxText(s string) string { trimmed := strings.TrimSpace(s) if trimmed == "" { return "-" } return trimmed } func safeTxRefNumber(s *string) string { if s == nil { return "-" } return safeTxText(*s) } func safeTxBank(p entity.Payment) string { if p.BankWarehouse.Id == 0 { return "-" } return safeTxText(p.BankWarehouse.Name) } func safeTxBankAccount(p entity.Payment) string { if p.BankWarehouse.Id == 0 { return "-" } return safeTxText(p.BankWarehouse.AccountNumber) } func formatTxDate(t time.Time) string { if t.IsZero() { return "-" } loc, err := time.LoadLocation("Asia/Jakarta") if err == nil { t = t.In(loc) } return t.Format("02-01-2006") } func formatTxStatus(p entity.Payment) string { if p.LatestApproval == nil { return "-" } return safeTxText(p.LatestApproval.StepName) } func txTransactionType(p entity.Payment) string { if p.TransactionType != "" { return p.TransactionType } return p.Direction } func txPartyName(p entity.Payment) string { switch p.PartyType { case "CUSTOMER": if p.Customer != nil && p.Customer.Id != 0 { return p.Customer.Name } case "SUPPLIER": if p.Supplier != nil && p.Supplier.Id != 0 { return p.Supplier.Name } } return "" } func txCreatedBy(p entity.Payment) string { if p.CreatedUser.Id == 0 { return "" } return p.CreatedUser.Name } func txAmounts(direction string, nominal float64) (income, expense float64) { switch strings.ToUpper(direction) { case "IN": return nominal, 0 case "OUT": return 0, nominal default: return 0, 0 } }