Files
lti-api/internal/modules/repports/controllers/repport.export.go
T
2026-04-22 22:50:20 +07:00

425 lines
11 KiB
Go

package controller
import (
"fmt"
"sort"
"strconv"
"strings"
"time"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
"gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto"
"github.com/gofiber/fiber/v2"
"github.com/xuri/excelize/v2"
)
const expenseReportExportSheetName = "Expense Reports"
var expenseReportTemplateSheetOrder = []string{
"UANG MAKAN",
"UPAH",
"EKSPEDISI ADE",
"GALON",
"GAS",
"KEBUTUHAN",
"EKSPEDISI LTI",
"KONTRIBUSI",
"PRODUKSI",
"KOMPENSASI",
"LAIN-LAIN",
"PERBAIKAN",
"LISTRIK",
"PAJAK",
"SOLAR",
}
var expenseReportSheetAliasMap = map[string]string{
"TRANSPORT 2": "EKSPEDISI ADE",
"TRANSPORT": "EKSPEDISI LTI",
"GAS BROODING": "GAS",
}
func isAllExpenseExcelExportRequest(c *fiber.Ctx) bool {
return strings.EqualFold(strings.TrimSpace(c.Query("export")), "excel") &&
strings.EqualFold(strings.TrimSpace(c.Query("type")), "all")
}
func exportExpenseReportListExcel(c *fiber.Ctx, items []dto.RepportExpenseListDTO) error {
content, err := buildExpenseReportExportWorkbook(items)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "failed to generate excel file")
}
filename := fmt.Sprintf("reports_expense_all_%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 buildExpenseReportExportWorkbook(items []dto.RepportExpenseListDTO) ([]byte, error) {
file := excelize.NewFile()
defer file.Close()
defaultSheet := file.GetSheetName(file.GetActiveSheetIndex())
groups := groupExpenseReportRowsBySheet(items)
orderedSheetNames := orderExpenseReportSheetNames(groups)
if len(orderedSheetNames) == 0 {
if defaultSheet != expenseReportExportSheetName {
if err := file.SetSheetName(defaultSheet, expenseReportExportSheetName); err != nil {
return nil, err
}
}
if err := writeExpenseReportSheet(file, expenseReportExportSheetName, []dto.RepportExpenseListDTO{}); err != nil {
return nil, err
}
} else {
for idx, sheetName := range orderedSheetNames {
if idx == 0 {
if defaultSheet != sheetName {
if err := file.SetSheetName(defaultSheet, sheetName); err != nil {
return nil, err
}
}
} else {
if _, err := file.NewSheet(sheetName); err != nil {
return nil, err
}
}
if err := writeExpenseReportSheet(file, sheetName, groups[sheetName]); err != nil {
return nil, err
}
}
}
buffer, err := file.WriteToBuffer()
if err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
func groupExpenseReportRowsBySheet(items []dto.RepportExpenseListDTO) map[string][]dto.RepportExpenseListDTO {
groups := make(map[string][]dto.RepportExpenseListDTO)
for _, item := range items {
product := resolveExpenseReportProduct(item)
sheetName := resolveExpenseReportSheetName(product)
groups[sheetName] = append(groups[sheetName], item)
}
return groups
}
func orderExpenseReportSheetNames(groups map[string][]dto.RepportExpenseListDTO) []string {
if len(groups) == 0 {
return nil
}
templateSet := make(map[string]struct{}, len(expenseReportTemplateSheetOrder))
ordered := make([]string, 0, len(groups))
for _, sheet := range expenseReportTemplateSheetOrder {
templateSet[sheet] = struct{}{}
if _, ok := groups[sheet]; ok {
ordered = append(ordered, sheet)
}
}
extras := make([]string, 0)
for sheet := range groups {
if _, ok := templateSet[sheet]; !ok {
extras = append(extras, sheet)
}
}
sort.Slice(extras, func(i, j int) bool {
return strings.ToUpper(extras[i]) < strings.ToUpper(extras[j])
})
ordered = append(ordered, extras...)
return ordered
}
func resolveExpenseReportSheetName(product string) string {
normalizedProduct := strings.ToUpper(strings.TrimSpace(product))
if alias, exists := expenseReportSheetAliasMap[normalizedProduct]; exists {
return alias
}
if normalizedProduct == "" {
normalizedProduct = "-"
}
return sanitizeExpenseReportSheetName(normalizedProduct)
}
func sanitizeExpenseReportSheetName(name string) string {
replacer := strings.NewReplacer(
":", " ",
"\\", " ",
"/", " ",
"?", " ",
"*", " ",
"[", " ",
"]", " ",
)
sanitized := strings.TrimSpace(replacer.Replace(name))
if sanitized == "" {
sanitized = "Sheet"
}
runes := []rune(sanitized)
if len(runes) > 31 {
sanitized = string(runes[:31])
}
return sanitized
}
func writeExpenseReportSheet(file *excelize.File, sheet string, items []dto.RepportExpenseListDTO) error {
if err := setExpenseReportTemplateColumns(file, sheet); err != nil {
return err
}
if err := setExpenseReportTemplateHeaders(file, sheet); err != nil {
return err
}
return setExpenseReportTemplateRows(file, sheet, items)
}
func setExpenseReportTemplateColumns(file *excelize.File, sheet string) error {
columnWidths := map[string]float64{
"A": 5.83203125,
"B": 20.83203125,
"C": 20.83203125,
"D": 15.83203125,
"E": 15.83203125,
"F": 15.83203125,
"G": 30.83203125,
"H": 20.83203125,
"I": 15.83203125,
"J": 15.83203125,
"K": 15.83203125,
"L": 20.83203125,
"M": 15.83203125,
"N": 20.83203125,
}
for col, width := range columnWidths {
if err := file.SetColWidth(sheet, col, col, width); err != nil {
return err
}
}
return nil
}
func setExpenseReportTemplateHeaders(file *excelize.File, sheet string) error {
headers := []string{
"No",
"No. PO",
"No. Referensi",
"Tanggal Realisasi",
"Tanggal Transaksi",
"Kategori",
"Produk",
"Lokasi",
"Kandang",
"Qty Pengajuan",
"Harga Pengajuan",
"Total Pengajuan",
"Qty Realisasi",
"Harga Realisasi",
"Total Realisasi",
"Status Pencairan",
}
for i, header := range headers {
columnName, err := excelize.ColumnNumberToName(i + 1)
if err != nil {
return err
}
if err := file.SetCellValue(sheet, columnName+"1", header); err != nil {
return err
}
}
return nil
}
func setExpenseReportTemplateRows(file *excelize.File, sheet string, items []dto.RepportExpenseListDTO) error {
totalQtyPengajuan := 0.0
totalPengajuan := 0.0
totalQtyRealisasi := 0.0
totalRealisasi := 0.0
for idx, item := range items {
row := idx + 2
rowString := strconv.Itoa(row)
produk := resolveExpenseReportProduct(item)
status := formatExpenseReportStatus(item)
lokasi := resolveExpenseReportLocation(item)
kandang := resolveExpenseReportKandang(item)
if err := file.SetCellValue(sheet, "A"+rowString, idx+1); err != nil {
return err
}
if err := file.SetCellValue(sheet, "B"+rowString, safeExpenseReportExportText(item.PoNumber)); err != nil {
return err
}
if err := file.SetCellValue(sheet, "C"+rowString, safeExpenseReportExportText(item.ReferenceNumber)); err != nil {
return err
}
if err := file.SetCellValue(sheet, "D"+rowString, formatExpenseReportOptionalDate(item.RealizationDate)); err != nil {
return err
}
if err := file.SetCellValue(sheet, "E"+rowString, formatExpenseReportDate(item.TransactionDate)); err != nil {
return err
}
if err := file.SetCellValue(sheet, "F"+rowString, safeExpenseReportExportText(item.Category)); err != nil {
return err
}
if err := file.SetCellValue(sheet, "G"+rowString, produk); err != nil {
return err
}
if err := file.SetCellValue(sheet, "H"+rowString, lokasi); err != nil {
return err
}
if err := file.SetCellValue(sheet, "I"+rowString, kandang); err != nil {
return err
}
if err := file.SetCellValue(sheet, "J"+rowString, item.Pengajuan.Qty); err != nil {
return err
}
if err := file.SetCellValue(sheet, "K"+rowString, item.Pengajuan.Price); err != nil {
return err
}
if err := file.SetCellValue(sheet, "L"+rowString, item.TotalPengajuan); err != nil {
return err
}
if err := file.SetCellValue(sheet, "M"+rowString, item.Realisasi.Qty); err != nil {
return err
}
if err := file.SetCellValue(sheet, "N"+rowString, item.Realisasi.Price); err != nil {
return err
}
if err := file.SetCellValue(sheet, "O"+rowString, item.TotalRealisasi); err != nil {
return err
}
if err := file.SetCellValue(sheet, "P"+rowString, status); err != nil {
return err
}
totalQtyPengajuan += item.Pengajuan.Qty
totalPengajuan += item.TotalPengajuan
totalQtyRealisasi += item.Realisasi.Qty
totalRealisasi += item.TotalRealisasi
}
totalRow := strconv.Itoa(len(items) + 2)
if err := file.SetCellValue(sheet, "A"+totalRow, "Total"); err != nil {
return err
}
if err := file.SetCellValue(sheet, "J"+totalRow, totalQtyPengajuan); err != nil {
return err
}
if err := file.SetCellValue(sheet, "K"+totalRow, 0); err != nil {
return err
}
if err := file.SetCellValue(sheet, "L"+totalRow, totalPengajuan); err != nil {
return err
}
if err := file.SetCellValue(sheet, "M"+totalRow, totalQtyRealisasi); err != nil {
return err
}
if err := file.SetCellValue(sheet, "N"+totalRow, 0); err != nil {
return err
}
if err := file.SetCellValue(sheet, "O"+totalRow, totalRealisasi); err != nil {
return err
}
return nil
}
func resolveExpenseReportProduct(item dto.RepportExpenseListDTO) string {
if item.Realisasi.Nonstock != nil {
name := strings.TrimSpace(item.Realisasi.Nonstock.Name)
if name != "" {
return name
}
}
if item.Pengajuan.Nonstock != nil {
name := strings.TrimSpace(item.Pengajuan.Nonstock.Name)
if name != "" {
return name
}
}
return "-"
}
func resolveExpenseReportLocation(item dto.RepportExpenseListDTO) string {
if item.Kandang != nil && item.Kandang.Location != nil {
name := strings.TrimSpace(item.Kandang.Location.Name)
if name != "" {
return name
}
}
return "-"
}
func resolveExpenseReportKandang(item dto.RepportExpenseListDTO) string {
if item.Kandang != nil {
name := strings.TrimSpace(item.Kandang.Name)
if name != "" {
return name
}
}
return "-"
}
func formatExpenseReportStatus(item dto.RepportExpenseListDTO) string {
if item.LatestApproval == nil {
return "-"
}
if item.LatestApproval.Action != nil &&
strings.EqualFold(strings.TrimSpace(*item.LatestApproval.Action), string(entity.ApprovalActionRejected)) {
return "Ditolak"
}
stepName := strings.TrimSpace(item.LatestApproval.StepName)
if stepName == "" {
return "-"
}
return stepName
}
func formatExpenseReportDate(value time.Time) string {
if value.IsZero() {
return "-"
}
location, err := time.LoadLocation("Asia/Jakarta")
if err == nil {
value = value.In(location)
}
return value.Format("02 Jan 2006")
}
func formatExpenseReportOptionalDate(value *time.Time) string {
if value == nil || value.IsZero() {
return "-"
}
return formatExpenseReportDate(*value)
}
func safeExpenseReportExportText(value string) string {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return "-"
}
return trimmed
}