mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'feat/export-recording' into 'development'
[FEAT][BE]: add export excel to get all recording See merge request mbugroup/lti-api!405
This commit is contained in:
@@ -26,6 +26,7 @@ func NewRecordingController(recordingService service.RecordingService) *Recordin
|
|||||||
|
|
||||||
func (u *RecordingController) GetAll(c *fiber.Ctx) error {
|
func (u *RecordingController) GetAll(c *fiber.Ctx) error {
|
||||||
projectFlockID := c.QueryInt("project_flock_kandang_id", 0)
|
projectFlockID := c.QueryInt("project_flock_kandang_id", 0)
|
||||||
|
exportType := strings.TrimSpace(c.Query("export"))
|
||||||
|
|
||||||
page := c.QueryInt("page", 1)
|
page := c.QueryInt("page", 1)
|
||||||
limit := c.QueryInt("limit", 10)
|
limit := c.QueryInt("limit", 10)
|
||||||
@@ -46,6 +47,11 @@ func (u *RecordingController) GetAll(c *fiber.Ctx) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listDTO := dto.ToRecordingListDTOs(result)
|
||||||
|
if strings.EqualFold(exportType, "excel") {
|
||||||
|
return exportRecordingListExcel(c, listDTO)
|
||||||
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).
|
return c.Status(fiber.StatusOK).
|
||||||
JSON(response.SuccessWithPaginate[dto.RecordingListDTO]{
|
JSON(response.SuccessWithPaginate[dto.RecordingListDTO]{
|
||||||
Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
@@ -57,7 +63,7 @@ func (u *RecordingController) GetAll(c *fiber.Ctx) error {
|
|||||||
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))),
|
||||||
TotalResults: totalResults,
|
TotalResults: totalResults,
|
||||||
},
|
},
|
||||||
Data: dto.ToRecordingListDTOs(result),
|
Data: listDTO,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,517 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/dto"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/xuri/excelize/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func exportRecordingListExcel(c *fiber.Ctx, items []dto.RecordingListDTO) error {
|
||||||
|
file := excelize.NewFile()
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
const sheetName = "Recordings"
|
||||||
|
defaultSheet := file.GetSheetName(file.GetActiveSheetIndex())
|
||||||
|
if defaultSheet != sheetName {
|
||||||
|
if err := file.SetSheetName(defaultSheet, sheetName); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to prepare excel sheet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := setRecordingExportColumns(file, sheetName); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to prepare excel columns")
|
||||||
|
}
|
||||||
|
if err := setRecordingExportHeaders(file, sheetName); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to prepare excel headers")
|
||||||
|
}
|
||||||
|
if err := setRecordingExportRows(file, sheetName, items); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to prepare excel rows")
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer, err := file.WriteToBuffer()
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusInternalServerError, "failed to generate excel file")
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("recordings_%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(buffer.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func setRecordingExportColumns(file *excelize.File, sheet string) error {
|
||||||
|
columnWidths := map[string]float64{
|
||||||
|
"A": 6,
|
||||||
|
"B": 18,
|
||||||
|
"C": 24,
|
||||||
|
"D": 18,
|
||||||
|
"E": 10,
|
||||||
|
"F": 12,
|
||||||
|
"G": 20,
|
||||||
|
"H": 18,
|
||||||
|
"I": 16,
|
||||||
|
"J": 12,
|
||||||
|
"K": 12,
|
||||||
|
"L": 16,
|
||||||
|
"M": 16,
|
||||||
|
"N": 18,
|
||||||
|
"O": 18,
|
||||||
|
"P": 16,
|
||||||
|
"Q": 16,
|
||||||
|
"R": 16,
|
||||||
|
"S": 16,
|
||||||
|
"T": 16,
|
||||||
|
"U": 16,
|
||||||
|
"V": 16,
|
||||||
|
"W": 18,
|
||||||
|
"X": 18,
|
||||||
|
"Y": 18,
|
||||||
|
"Z": 22,
|
||||||
|
"AA": 16,
|
||||||
|
"AB": 18,
|
||||||
|
}
|
||||||
|
|
||||||
|
for col, width := range columnWidths {
|
||||||
|
if err := file.SetColWidth(sheet, col, col, width); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := file.SetRowHeight(sheet, 1, 30); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetRowHeight(sheet, 2, 30); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setRecordingExportHeaders(file *excelize.File, sheet string) error {
|
||||||
|
verticalHeaderCols := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "Y", "Z", "AA", "AB"}
|
||||||
|
for _, col := range verticalHeaderCols {
|
||||||
|
if err := file.MergeCell(sheet, col+"1", col+"2"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headerValues := map[string]string{
|
||||||
|
"A1": "No",
|
||||||
|
"B1": "Lokasi",
|
||||||
|
"C1": "Flock",
|
||||||
|
"D1": "Kandang",
|
||||||
|
"E1": "Periode",
|
||||||
|
"F1": "Kategori",
|
||||||
|
"G1": "Umur (hari)",
|
||||||
|
"H1": "Waktu Recording",
|
||||||
|
"I1": "Populasi Akhir",
|
||||||
|
"Y1": "Status Approval",
|
||||||
|
"Z1": "Catatan Approval",
|
||||||
|
"AA1": "Dibuat Oleh",
|
||||||
|
"AB1": "Tanggal Submit",
|
||||||
|
}
|
||||||
|
for cell, value := range headerValues {
|
||||||
|
if err := file.SetCellValue(sheet, cell, value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := file.MergeCell(sheet, "J1", "K1"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "J1", "FCR"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "J2", "Actual"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "K2", "Standard"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := file.MergeCell(sheet, "L1", "M1"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "L1", "Feed Intake (KG)"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "L2", "Actual"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "M2", "Standard"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := file.MergeCell(sheet, "N1", "P1"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "N1", "Mortality"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "N2", "Cum Depletion Rate"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "O2", "Max Depletion Std"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "P2", "Total Depletion"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := file.MergeCell(sheet, "Q1", "T1"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "Q1", "Egg Production"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "Q2", "Egg Mass Actual"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "R2", "Egg Mass Standar"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "S2", "Egg Weight Actual"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "T2", "Egg Weight Standar"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := file.MergeCell(sheet, "U1", "X1"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "U1", "Hen Performance"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "U2", "Hen Day Actual"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "V2", "Hen Day Standar"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "W2", "Hen House Actual"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellValue(sheet, "X2", "Hen House Standar"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
headerStyle, err := file.NewStyle(&excelize.Style{
|
||||||
|
Font: &excelize.Font{
|
||||||
|
Bold: true,
|
||||||
|
Color: "7A7A7A",
|
||||||
|
},
|
||||||
|
Fill: excelize.Fill{
|
||||||
|
Type: "pattern",
|
||||||
|
Pattern: 1,
|
||||||
|
Color: []string{"F5F5F5"},
|
||||||
|
},
|
||||||
|
Alignment: &excelize.Alignment{
|
||||||
|
Horizontal: "center",
|
||||||
|
Vertical: "center",
|
||||||
|
WrapText: true,
|
||||||
|
},
|
||||||
|
Border: []excelize.Border{
|
||||||
|
{Type: "left", Color: "DDDDDD", Style: 1},
|
||||||
|
{Type: "top", Color: "DDDDDD", Style: 1},
|
||||||
|
{Type: "bottom", Color: "DDDDDD", Style: 1},
|
||||||
|
{Type: "right", Color: "DDDDDD", Style: 1},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.SetCellStyle(sheet, "A1", "AB2", headerStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setRecordingExportRows(file *excelize.File, sheet string, items []dto.RecordingListDTO) error {
|
||||||
|
if len(items) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
columns := []string{
|
||||||
|
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
|
||||||
|
"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "AA", "AB",
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, item := range items {
|
||||||
|
rowNumber := i + 3
|
||||||
|
|
||||||
|
fcrStd := 0.0
|
||||||
|
if item.ProjectFlock.Fcr != nil {
|
||||||
|
fcrStd = item.ProjectFlock.Fcr.FcrStd
|
||||||
|
}
|
||||||
|
|
||||||
|
maxDepletionStd := 0.0
|
||||||
|
eggMassStd := 0.0
|
||||||
|
eggWeightStd := 0.0
|
||||||
|
henDayStd := 0.0
|
||||||
|
henHouseStd := 0.0
|
||||||
|
feedIntakeStd := 0.0
|
||||||
|
if item.ProjectFlock.ProductionStandart != nil {
|
||||||
|
maxDepletionStd = item.ProjectFlock.ProductionStandart.MaxDepletionStd
|
||||||
|
eggMassStd = item.ProjectFlock.ProductionStandart.EggMassStd
|
||||||
|
eggWeightStd = item.ProjectFlock.ProductionStandart.EggWeightStd
|
||||||
|
henDayStd = item.ProjectFlock.ProductionStandart.HenDayStd
|
||||||
|
henHouseStd = item.ProjectFlock.ProductionStandart.HenHouseStd
|
||||||
|
feedIntakeStd = item.ProjectFlock.ProductionStandart.FeedIntakeStd
|
||||||
|
}
|
||||||
|
|
||||||
|
locationName := "-"
|
||||||
|
if item.Location != nil {
|
||||||
|
locationName = safeExportText(item.Location.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
kandangName := "-"
|
||||||
|
if item.Kandang != nil {
|
||||||
|
kandangName = safeExportText(item.Kandang.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
createdBy := "-"
|
||||||
|
if item.CreatedUser != nil {
|
||||||
|
createdBy = safeExportText(item.CreatedUser.Name)
|
||||||
|
} else if strings.TrimSpace(item.Approval.ActionBy.Name) != "" {
|
||||||
|
createdBy = safeExportText(item.Approval.ActionBy.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
rowValues := []interface{}{
|
||||||
|
i + 1,
|
||||||
|
locationName,
|
||||||
|
safeExportText(item.ProjectFlock.FlockName),
|
||||||
|
kandangName,
|
||||||
|
item.ProjectFlock.Period,
|
||||||
|
formatCategoryLabel(item.ProjectFlock.ProjectFlockCategory),
|
||||||
|
formatAgeLabel(item),
|
||||||
|
formatDateIndonesian(item.RecordDatetime),
|
||||||
|
formatNumberID(item.ProjectFlock.TotalChickQty, 0, false),
|
||||||
|
formatNumberID(item.FcrValue, 2, true),
|
||||||
|
formatNumberID(fcrStd, 2, true),
|
||||||
|
formatNumberID(item.FeedIntake, 2, true),
|
||||||
|
formatNumberID(feedIntakeStd, 2, true),
|
||||||
|
formatPercentID(item.CumDepletionRate, 2),
|
||||||
|
formatPercentID(maxDepletionStd, 2),
|
||||||
|
formatNumberID(item.TotalDepletionQty, 2, true),
|
||||||
|
formatNumberID(item.EggMass, 2, true),
|
||||||
|
formatNumberID(eggMassStd, 2, true),
|
||||||
|
formatNumberID(item.EggWeight, 2, true),
|
||||||
|
formatNumberID(eggWeightStd, 2, true),
|
||||||
|
formatPercentID(item.HenDay, 2),
|
||||||
|
formatPercentID(henDayStd, 2),
|
||||||
|
formatPercentID(item.HenHouse, 2),
|
||||||
|
formatPercentID(henHouseStd, 2),
|
||||||
|
formatApprovalStatus(item),
|
||||||
|
safeExportText(pointerString(item.Approval.Notes)),
|
||||||
|
createdBy,
|
||||||
|
formatDateIndonesian(item.CreatedAt),
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, col := range columns {
|
||||||
|
cell := fmt.Sprintf("%s%d", col, rowNumber)
|
||||||
|
if err := file.SetCellValue(sheet, cell, rowValues[idx]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastRow := len(items) + 2
|
||||||
|
dataCenterStyle, err := file.NewStyle(&excelize.Style{
|
||||||
|
Alignment: &excelize.Alignment{
|
||||||
|
Horizontal: "center",
|
||||||
|
Vertical: "center",
|
||||||
|
WrapText: true,
|
||||||
|
},
|
||||||
|
Border: []excelize.Border{
|
||||||
|
{Type: "left", Color: "E6E6E6", Style: 1},
|
||||||
|
{Type: "top", Color: "E6E6E6", Style: 1},
|
||||||
|
{Type: "bottom", Color: "E6E6E6", Style: 1},
|
||||||
|
{Type: "right", Color: "E6E6E6", Style: 1},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.SetCellStyle(sheet, "A3", fmt.Sprintf("AB%d", lastRow), dataCenterStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dataLeftStyle, err := file.NewStyle(&excelize.Style{
|
||||||
|
Alignment: &excelize.Alignment{
|
||||||
|
Horizontal: "left",
|
||||||
|
Vertical: "center",
|
||||||
|
WrapText: true,
|
||||||
|
},
|
||||||
|
Border: []excelize.Border{
|
||||||
|
{Type: "left", Color: "E6E6E6", Style: 1},
|
||||||
|
{Type: "top", Color: "E6E6E6", Style: 1},
|
||||||
|
{Type: "bottom", Color: "E6E6E6", Style: 1},
|
||||||
|
{Type: "right", Color: "E6E6E6", Style: 1},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
leftColumns := []string{"B", "C", "D", "F", "G", "H", "Y", "Z", "AA", "AB"}
|
||||||
|
for _, col := range leftColumns {
|
||||||
|
if err := file.SetCellStyle(sheet, col+"3", fmt.Sprintf("%s%d", col, lastRow), dataLeftStyle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatAgeLabel(item dto.RecordingListDTO) string {
|
||||||
|
if item.Day <= 0 {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
week := 0
|
||||||
|
if item.ProjectFlock.ProductionStandart != nil && item.ProjectFlock.ProductionStandart.Week > 0 {
|
||||||
|
week = item.ProjectFlock.ProductionStandart.Week
|
||||||
|
} else {
|
||||||
|
week = ((item.Day - 1) / 7) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%d (Minggu ke-%d)", item.Day, week)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatDateIndonesian(t time.Time) string {
|
||||||
|
if t.IsZero() {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
loc, err := time.LoadLocation("Asia/Jakarta")
|
||||||
|
if err == nil {
|
||||||
|
t = t.In(loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
monthNames := []string{
|
||||||
|
"",
|
||||||
|
"Januari",
|
||||||
|
"Februari",
|
||||||
|
"Maret",
|
||||||
|
"April",
|
||||||
|
"Mei",
|
||||||
|
"Juni",
|
||||||
|
"Juli",
|
||||||
|
"Agustus",
|
||||||
|
"September",
|
||||||
|
"Oktober",
|
||||||
|
"November",
|
||||||
|
"Desember",
|
||||||
|
}
|
||||||
|
|
||||||
|
month := int(t.Month())
|
||||||
|
monthLabel := strconv.Itoa(month)
|
||||||
|
if month > 0 && month < len(monthNames) {
|
||||||
|
monthLabel = monthNames[month]
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%02d %s %d", t.Day(), monthLabel, t.Year())
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatCategoryLabel(value string) string {
|
||||||
|
normalized := strings.TrimSpace(strings.ReplaceAll(value, "_", " "))
|
||||||
|
if normalized == "" {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Fields(strings.ToLower(normalized))
|
||||||
|
for i, part := range parts {
|
||||||
|
if len(part) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts[i] = strings.ToUpper(part[:1]) + part[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(parts, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatPercentID(value float64, decimals int) string {
|
||||||
|
return fmt.Sprintf("%s%%", formatNumberID(value, decimals, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatNumberID(value float64, decimals int, trim bool) string {
|
||||||
|
if math.IsNaN(value) || math.IsInf(value, 0) {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
if decimals < 0 {
|
||||||
|
decimals = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := strconv.FormatFloat(value, 'f', decimals, 64)
|
||||||
|
if trim && strings.Contains(raw, ".") {
|
||||||
|
raw = strings.TrimRight(raw, "0")
|
||||||
|
raw = strings.TrimRight(raw, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(raw, ".", 2)
|
||||||
|
intPart := parts[0]
|
||||||
|
sign := ""
|
||||||
|
if strings.HasPrefix(intPart, "-") {
|
||||||
|
sign = "-"
|
||||||
|
intPart = strings.TrimPrefix(intPart, "-")
|
||||||
|
}
|
||||||
|
if intPart == "" {
|
||||||
|
intPart = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
var grouped strings.Builder
|
||||||
|
rem := len(intPart) % 3
|
||||||
|
if rem > 0 {
|
||||||
|
grouped.WriteString(intPart[:rem])
|
||||||
|
if len(intPart) > rem {
|
||||||
|
grouped.WriteString(".")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := rem; i < len(intPart); i += 3 {
|
||||||
|
grouped.WriteString(intPart[i : i+3])
|
||||||
|
if i+3 < len(intPart) {
|
||||||
|
grouped.WriteString(".")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := sign + grouped.String()
|
||||||
|
if len(parts) == 2 && parts[1] != "" {
|
||||||
|
result += "," + parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func safeExportText(value string) string {
|
||||||
|
normalized := strings.TrimSpace(value)
|
||||||
|
if normalized == "" {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
func pointerString(value *string) string {
|
||||||
|
if value == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *value
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatApprovalStatus(item dto.RecordingListDTO) string {
|
||||||
|
action := strings.ToUpper(strings.TrimSpace(pointerString(item.Approval.Action)))
|
||||||
|
switch action {
|
||||||
|
case "UPDATED":
|
||||||
|
return "Diperbarui"
|
||||||
|
case "CREATED":
|
||||||
|
return safeExportText(item.Approval.StepName)
|
||||||
|
default:
|
||||||
|
return safeExportText(item.Approval.StepName)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user