package controller import ( "bytes" "reflect" "strings" "testing" "time" approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto" kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto" locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto" nonstockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks/dto" supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto" "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto" "github.com/xuri/excelize/v2" ) func TestBuildExpenseReportExportWorkbookHeadersAndRows(t *testing.T) { realizationDate := time.Date(2026, time.April, 23, 0, 0, 0, 0, time.UTC) items := []dto.RepportExpenseListDTO{ buildExpenseExportTestItem( "REF-0001", "PO-0001", "BOP", "UPAH", "Darawati", "Darawati C1", time.Date(2026, time.April, 22, 0, 0, 0, 0, time.UTC), &realizationDate, 2, 10000, 20000, 2, 9000, 18000, "Realisasi", nil, ), buildExpenseExportTestItem( "REF-0002", "PO-0002", "BOP", "TRANSPORT 2", "", "", time.Date(2026, time.April, 22, 0, 0, 0, 0, time.UTC), &realizationDate, 1, 50000, 50000, 1, 50000, 50000, "Pengajuan", strPtr("REJECTED"), ), buildExpenseExportTestItem( "REF-0003", "PO-0003", "BOP", "TRANSPORT", "Jamali", "Jamali 1", time.Date(2026, time.April, 22, 0, 0, 0, 0, time.UTC), &realizationDate, 3, 12000, 36000, 2, 11000, 22000, "Selesai", nil, ), buildExpenseExportTestItem( "REF-0004", "PO-0004", "BOP", "TRANSPORT", "Jamali", "Jamali 2", time.Date(2026, time.April, 22, 0, 0, 0, 0, time.UTC), &realizationDate, 1, 8000, 8000, 1, 8000, 8000, "Realisasi", nil, ), buildExpenseExportTestItem( "REF-0005", "PO-0005", "BOP", "ZZZ CUSTOM", "", "", time.Date(2026, time.April, 22, 0, 0, 0, 0, time.UTC), nil, 1, 7000, 7000, 1, 7000, 7000, "", nil, ), } content, err := buildExpenseReportExportWorkbook(items) if err != nil { t.Fatalf("buildExpenseReportExportWorkbook returned error: %v", err) } file, err := excelize.OpenReader(bytes.NewReader(content)) if err != nil { t.Fatalf("failed to open workbook bytes: %v", err) } defer file.Close() expectedSheetOrder := []string{"UPAH", "EKSPEDISI ADE", "EKSPEDISI LTI", "ZZZ CUSTOM"} if got := file.GetSheetList(); !reflect.DeepEqual(got, expectedSheetOrder) { t.Fatalf("unexpected sheet order: got %v expected %v", got, expectedSheetOrder) } expectedHeaders := map[string]string{ "A1": "No", "B1": "No. PO", "C1": "No. Referensi", "D1": "Tanggal Realisasi", "E1": "Tanggal Transaksi", "F1": "Kategori", "G1": "Produk", "H1": "Lokasi", "I1": "Kandang", "J1": "Qty Pengajuan", "K1": "Harga Pengajuan", "L1": "Total Pengajuan", "M1": "Qty Realisasi", "N1": "Harga Realisasi", "O1": "Total Realisasi", "P1": "Status Pencairan", } for cell, expected := range expectedHeaders { assertExpenseSheetCellEquals(t, file, "UPAH", cell, expected) } assertExpenseSheetCellEquals(t, file, "UPAH", "A2", "1") assertExpenseSheetCellEquals(t, file, "UPAH", "B2", "PO-0001") assertExpenseSheetCellEquals(t, file, "UPAH", "C2", "REF-0001") assertExpenseSheetCellEquals(t, file, "UPAH", "D2", "23 Apr 2026") assertExpenseSheetCellEquals(t, file, "UPAH", "E2", "22 Apr 2026") assertExpenseSheetCellEquals(t, file, "UPAH", "F2", "BOP") assertExpenseSheetCellEquals(t, file, "UPAH", "G2", "UPAH") assertExpenseSheetCellEquals(t, file, "UPAH", "H2", "Darawati") assertExpenseSheetCellEquals(t, file, "UPAH", "I2", "Darawati C1") assertExpenseSheetCellEquals(t, file, "UPAH", "J2", "2") assertExpenseSheetCellEquals(t, file, "UPAH", "K2", "10000") assertExpenseSheetCellEquals(t, file, "UPAH", "L2", "20000") assertExpenseSheetCellEquals(t, file, "UPAH", "M2", "2") assertExpenseSheetCellEquals(t, file, "UPAH", "N2", "9000") assertExpenseSheetCellEquals(t, file, "UPAH", "O2", "18000") assertExpenseSheetCellEquals(t, file, "UPAH", "P2", "Realisasi") assertExpenseSheetCellEquals(t, file, "UPAH", "A3", "Total") assertExpenseSheetCellEquals(t, file, "UPAH", "J3", "2") assertExpenseSheetCellEquals(t, file, "UPAH", "K3", "0") assertExpenseSheetCellEquals(t, file, "UPAH", "L3", "20000") assertExpenseSheetCellEquals(t, file, "UPAH", "M3", "2") assertExpenseSheetCellEquals(t, file, "UPAH", "N3", "0") assertExpenseSheetCellEquals(t, file, "UPAH", "O3", "18000") assertExpenseSheetCellEquals(t, file, "EKSPEDISI ADE", "G2", "TRANSPORT 2") assertExpenseSheetCellEquals(t, file, "EKSPEDISI ADE", "P2", "Ditolak") assertExpenseSheetCellEquals(t, file, "EKSPEDISI ADE", "A3", "Total") assertExpenseSheetCellEquals(t, file, "EKSPEDISI LTI", "A2", "1") assertExpenseSheetCellEquals(t, file, "EKSPEDISI LTI", "A3", "2") assertExpenseSheetCellEquals(t, file, "EKSPEDISI LTI", "G2", "TRANSPORT") assertExpenseSheetCellEquals(t, file, "EKSPEDISI LTI", "G3", "TRANSPORT") assertExpenseSheetCellEquals(t, file, "EKSPEDISI LTI", "A4", "Total") assertExpenseSheetCellEquals(t, file, "EKSPEDISI LTI", "J4", "4") assertExpenseSheetCellEquals(t, file, "EKSPEDISI LTI", "L4", "44000") assertExpenseSheetCellEquals(t, file, "EKSPEDISI LTI", "M4", "3") assertExpenseSheetCellEquals(t, file, "EKSPEDISI LTI", "O4", "30000") assertExpenseSheetCellEquals(t, file, "ZZZ CUSTOM", "H2", "-") assertExpenseSheetCellEquals(t, file, "ZZZ CUSTOM", "I2", "-") assertExpenseSheetCellEquals(t, file, "ZZZ CUSTOM", "D2", "-") assertExpenseSheetCellEquals(t, file, "ZZZ CUSTOM", "P2", "-") for _, cell := range []string{"K2", "L2", "N2", "O2"} { val, err := file.GetCellValue("UPAH", cell) if err != nil { t.Fatalf("GetCellValue(UPAH,%s) failed: %v", cell, err) } if strings.Contains(val, "Rp") { t.Fatalf("expected numeric plain value in %s, got %q", cell, val) } } } func assertExpenseSheetCellEquals(t *testing.T, file *excelize.File, sheet, cell, expected string) { t.Helper() got, err := file.GetCellValue(sheet, cell) if err != nil { t.Fatalf("GetCellValue(%s,%s) failed: %v", sheet, cell, err) } if got != expected { t.Fatalf("expected %s!%s=%q, got %q", sheet, cell, expected, got) } } func buildExpenseExportTestItem( reference, poNumber, category, product, location, kandang string, transactionDate time.Time, realizationDate *time.Time, qtyPengajuan, hargaPengajuan, totalPengajuan, qtyRealisasi, hargaRealisasi, totalRealisasi float64, stepName string, action *string, ) dto.RepportExpenseListDTO { item := dto.RepportExpenseListDTO{ RepportExpenseBaseDTO: dto.RepportExpenseBaseDTO{ ReferenceNumber: reference, PoNumber: poNumber, Category: category, TransactionDate: transactionDate, RealizationDate: realizationDate, Supplier: &supplierDTO.SupplierRelationDTO{ Name: "Supplier A", }, }, Pengajuan: dto.RepportExpensePengajuanDTO{ Qty: qtyPengajuan, Price: hargaPengajuan, Nonstock: &nonstockDTO.NonstockRelationDTO{ Name: product, }, }, Realisasi: dto.RepportExpenseRealisasiDTO{ Qty: qtyRealisasi, Price: hargaRealisasi, Nonstock: &nonstockDTO.NonstockRelationDTO{ Name: product, }, }, TotalPengajuan: totalPengajuan, TotalRealisasi: totalRealisasi, LatestApproval: &approvalDTO.ApprovalRelationDTO{ StepName: stepName, Action: action, }, } if kandang != "" { item.Kandang = &kandangDTO.KandangRelationDTO{Name: kandang} if location != "" { item.Kandang.Location = &locationDTO.LocationRelationDTO{Name: location} } } return item } func strPtr(value string) *string { return &value }