mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-22 22:35:43 +00:00
add export excel from api
This commit is contained in:
@@ -0,0 +1,424 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user