diff --git a/internal/modules/purchases/controllers/purchase.export.go b/internal/modules/purchases/controllers/purchase.export.go index cf8ede38..046291df 100644 --- a/internal/modules/purchases/controllers/purchase.export.go +++ b/internal/modules/purchases/controllers/purchase.export.go @@ -3,13 +3,11 @@ package controller import ( "fmt" "math" - "sort" "strconv" "strings" "time" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" - "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/dto" "github.com/gofiber/fiber/v2" "github.com/xuri/excelize/v2" @@ -45,7 +43,6 @@ func buildPurchaseExportWorkbook(purchases []entity.Purchase) ([]byte, error) { } } - listItems := dto.ToPurchaseListDTOs(purchases) grandTotals := buildPurchaseGrandTotalMap(purchases) if err := setPurchaseExportColumns(file, purchaseExportSheetName); err != nil { @@ -54,7 +51,7 @@ func buildPurchaseExportWorkbook(purchases []entity.Purchase) ([]byte, error) { if err := setPurchaseExportHeaders(file, purchaseExportSheetName); err != nil { return nil, err } - if err := setPurchaseExportRows(file, purchaseExportSheetName, listItems, grandTotals); err != nil { + if err := setPurchaseExportRows(file, purchaseExportSheetName, purchases, grandTotals); err != nil { return nil, err } if err := file.SetPanes(purchaseExportSheetName, &excelize.Panes{ @@ -81,10 +78,11 @@ func setPurchaseExportColumns(file *excelize.File, sheet string) error { "D": 14, "E": 22, "F": 22, - "G": 18, - "H": 18, - "I": 52, - "J": 24, + "G": 22, + "H": 32, + "I": 18, + "J": 18, + "K": 24, } for col, width := range columnWidths { @@ -107,9 +105,10 @@ func setPurchaseExportHeaders(file *excelize.File, sheet string) error { "Tanggal Terima", "Supplier", "Lokasi", + "Gudang", + "Product", "Status", "Grand Total", - "Products", "Notes", } @@ -138,49 +137,34 @@ func setPurchaseExportHeaders(file *excelize.File, sheet string) error { return err } - return file.SetCellStyle(sheet, "A1", "J1", headerStyle) + return file.SetCellStyle(sheet, "A1", "K1", headerStyle) } -func setPurchaseExportRows(file *excelize.File, sheet string, items []dto.PurchaseListDTO, grandTotals map[uint]float64) error { - if len(items) == 0 { +func setPurchaseExportRows(file *excelize.File, sheet string, purchases []entity.Purchase, grandTotals map[uint]float64) error { + if len(purchases) == 0 { return nil } - for i, item := range items { - row := strconv.Itoa(i + 2) - if err := file.SetCellValue(sheet, "A"+row, safePurchaseExportText(item.PrNumber)); err != nil { - return err + rowIdx := 2 + for p := range purchases { + purchase := &purchases[p] + total := grandTotals[purchase.Id] + if len(purchase.Items) == 0 { + if err := writePurchaseExportRow(file, sheet, rowIdx, purchase, nil, total); err != nil { + return err + } + rowIdx++ + continue } - if err := file.SetCellValue(sheet, "B"+row, safePurchaseExportPointerText(item.PoNumber)); err != nil { - return err - } - if err := file.SetCellValue(sheet, "C"+row, formatPurchaseExportDate(item.PoDate)); err != nil { - return err - } - if err := file.SetCellValue(sheet, "D"+row, formatPurchaseExportDate(item.ReceivedDate)); err != nil { - return err - } - if err := file.SetCellValue(sheet, "E"+row, safePurchaseSupplierName(item)); err != nil { - return err - } - if err := file.SetCellValue(sheet, "F"+row, safePurchaseLocationName(item)); err != nil { - return err - } - if err := file.SetCellValue(sheet, "G"+row, formatPurchaseExportStatus(item)); err != nil { - return err - } - if err := file.SetCellValue(sheet, "H"+row, formatPurchaseRupiah(grandTotals[item.Id])); err != nil { - return err - } - if err := file.SetCellValue(sheet, "I"+row, formatPurchaseProducts(item)); err != nil { - return err - } - if err := file.SetCellValue(sheet, "J"+row, safePurchaseExportPointerText(item.Notes)); err != nil { - return err + for it := range purchase.Items { + if err := writePurchaseExportRow(file, sheet, rowIdx, purchase, &purchase.Items[it], total); err != nil { + return err + } + rowIdx++ } } - lastRow := len(items) + 1 + lastRow := rowIdx - 1 dataStyle, err := file.NewStyle(&excelize.Style{ Alignment: &excelize.Alignment{ Horizontal: "left", @@ -197,7 +181,7 @@ func setPurchaseExportRows(file *excelize.File, sheet string, items []dto.Purcha if err != nil { return err } - if err := file.SetCellStyle(sheet, "A2", "J"+strconv.Itoa(lastRow), dataStyle); err != nil { + if err := file.SetCellStyle(sheet, "A2", "K"+strconv.Itoa(lastRow), dataStyle); err != nil { return err } @@ -217,7 +201,59 @@ func setPurchaseExportRows(file *excelize.File, sheet string, items []dto.Purcha return err } - return file.SetCellStyle(sheet, "H2", "H"+strconv.Itoa(lastRow), moneyStyle) + return file.SetCellStyle(sheet, "J2", "J"+strconv.Itoa(lastRow), moneyStyle) +} + +func writePurchaseExportRow(file *excelize.File, sheet string, rowIdx int, purchase *entity.Purchase, item *entity.PurchaseItem, grandTotal float64) error { + row := strconv.Itoa(rowIdx) + + // Purchase-level columns (repeat across rows of the same purchase) + if err := file.SetCellValue(sheet, "A"+row, safePurchaseExportText(purchase.PrNumber)); err != nil { + return err + } + if err := file.SetCellValue(sheet, "B"+row, safePurchaseExportPointerText(purchase.PoNumber)); err != nil { + return err + } + if err := file.SetCellValue(sheet, "C"+row, formatPurchaseExportDate(purchase.PoDate)); err != nil { + return err + } + if err := file.SetCellValue(sheet, "E"+row, safePurchaseExportEntitySupplierName(purchase)); err != nil { + return err + } + if err := file.SetCellValue(sheet, "I"+row, formatPurchaseExportEntityStatus(purchase)); err != nil { + return err + } + if err := file.SetCellValue(sheet, "J"+row, formatPurchaseRupiah(grandTotal)); err != nil { + return err + } + if err := file.SetCellValue(sheet, "K"+row, safePurchaseExportPointerText(purchase.Notes)); err != nil { + return err + } + + // Item-level columns + if item == nil { + for _, col := range []string{"D", "F", "G", "H"} { + if err := file.SetCellValue(sheet, col+row, "-"); err != nil { + return err + } + } + return nil + } + + if err := file.SetCellValue(sheet, "D"+row, formatPurchaseExportDate(item.ReceivedDate)); err != nil { + return err + } + if err := file.SetCellValue(sheet, "F"+row, safePurchaseItemLocationName(item)); err != nil { + return err + } + if err := file.SetCellValue(sheet, "G"+row, safePurchaseWarehouseName(item)); err != nil { + return err + } + if err := file.SetCellValue(sheet, "H"+row, safePurchaseItemProductName(item)); err != nil { + return err + } + + return nil } func buildPurchaseGrandTotalMap(items []entity.Purchase) map[uint]float64 { @@ -232,31 +268,45 @@ func buildPurchaseGrandTotalMap(items []entity.Purchase) map[uint]float64 { return result } -func safePurchaseSupplierName(item dto.PurchaseListDTO) string { - if item.Supplier == nil { +func safePurchaseExportEntitySupplierName(purchase *entity.Purchase) string { + if purchase.Supplier.Id == 0 { return "-" } - return safePurchaseExportText(item.Supplier.Name) + return safePurchaseExportText(purchase.Supplier.Name) } -func safePurchaseLocationName(item dto.PurchaseListDTO) string { - if item.Location == nil { +func safePurchaseWarehouseName(item *entity.PurchaseItem) string { + if item.Warehouse == nil { return "-" } - return safePurchaseExportText(item.Location.Name) + return safePurchaseExportText(item.Warehouse.Name) } -func formatPurchaseExportStatus(item dto.PurchaseListDTO) string { - if item.LatestApproval == nil { +func safePurchaseItemLocationName(item *entity.PurchaseItem) string { + if item.Warehouse == nil || item.Warehouse.Location == nil { + return "-" + } + return safePurchaseExportText(item.Warehouse.Location.Name) +} + +func safePurchaseItemProductName(item *entity.PurchaseItem) string { + if item.Product == nil { + return "-" + } + return safePurchaseExportText(item.Product.Name) +} + +func formatPurchaseExportEntityStatus(purchase *entity.Purchase) string { + if purchase.LatestApproval == nil { return "-" } - if item.LatestApproval.Action != nil && - strings.EqualFold(strings.TrimSpace(*item.LatestApproval.Action), string(entity.ApprovalActionRejected)) { + if purchase.LatestApproval.Action != nil && + strings.EqualFold(strings.TrimSpace(string(*purchase.LatestApproval.Action)), string(entity.ApprovalActionRejected)) { return "Ditolak" } - return safePurchaseExportText(item.LatestApproval.StepName) + return safePurchaseExportText(purchase.LatestApproval.StepName) } func formatPurchaseExportDate(value *time.Time) string { @@ -273,33 +323,6 @@ func formatPurchaseExportDate(value *time.Time) string { return t.Format("02-01-2006") } -func formatPurchaseProducts(item dto.PurchaseListDTO) string { - if len(item.Products) == 0 { - return "-" - } - - seen := make(map[string]struct{}) - names := make([]string, 0, len(item.Products)) - for i := range item.Products { - name := strings.TrimSpace(item.Products[i].Name) - if name == "" { - continue - } - if _, exists := seen[name]; exists { - continue - } - seen[name] = struct{}{} - names = append(names, name) - } - - if len(names) == 0 { - return "-" - } - - sort.Strings(names) - return strings.Join(names, ", ") -} - func safePurchaseExportPointerText(value *string) string { if value == nil { return "-" diff --git a/internal/modules/purchases/dto/purchase.dto.go b/internal/modules/purchases/dto/purchase.dto.go index 22db3d7d..fd1c859a 100644 --- a/internal/modules/purchases/dto/purchase.dto.go +++ b/internal/modules/purchases/dto/purchase.dto.go @@ -31,6 +31,7 @@ type PurchaseListDTO struct { CreatedUser *userDTO.UserRelationDTO `json:"created_user"` RequesterName string `json:"requester_name"` PoExpedition []PoExpeditionDTO `json:"po_expedition"` + Items []PurchaseItemDTO `json:"items"` Products []productDTO.ProductRelationDTO `json:"products"` Location *locationDTO.LocationRelationDTO `json:"location"` Area *areaDTO.AreaRelationDTO `json:"area"` @@ -227,6 +228,7 @@ func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO { CreatedUser: createdUser, RequesterName: requesterName, PoExpedition: poExpedition, + Items: ToPurchaseItemDTOs(p.Items), Products: products, Location: location, Area: area,