mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
425 lines
11 KiB
Go
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
|
|
}
|