add export excel from api

This commit is contained in:
giovanni
2026-04-22 22:50:20 +07:00
parent ff630a1ed0
commit 3e99caf3a7
5 changed files with 946 additions and 0 deletions
@@ -0,0 +1,202 @@
package controller
import (
"bytes"
"io"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"time"
"github.com/gofiber/fiber/v2"
"github.com/xuri/excelize/v2"
"gorm.io/gorm"
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"
service "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/services"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/validations"
)
type repportServiceStub struct {
service.RepportService
getExpenseCalls []validation.ExpenseQuery
}
func (s *repportServiceStub) GetExpense(_ *fiber.Ctx, params *validation.ExpenseQuery) ([]dto.RepportExpenseListDTO, int64, error) {
callCopy := *params
callCopy.AllowedAreaIDs = append([]int64(nil), params.AllowedAreaIDs...)
callCopy.AllowedLocationIDs = append([]int64(nil), params.AllowedLocationIDs...)
s.getExpenseCalls = append(s.getExpenseCalls, callCopy)
switch params.Page {
case 1:
return []dto.RepportExpenseListDTO{
buildExpenseListForControllerTest("REF-00001", "TRANSPORT 2"),
buildExpenseListForControllerTest("REF-00002", "TRANSPORT"),
}, 3, nil
case 2:
return []dto.RepportExpenseListDTO{
buildExpenseListForControllerTest("REF-00003", "TRANSPORT"),
}, 3, nil
default:
return []dto.RepportExpenseListDTO{}, 3, nil
}
}
func (s *repportServiceStub) DB() *gorm.DB {
return nil
}
func TestRepportControllerGetExpenseExportAllIgnoresRequestLimit(t *testing.T) {
app := fiber.New()
stub := &repportServiceStub{}
ctrl := NewRepportController(stub)
app.Get("/reports/expense", ctrl.GetExpense)
req := httptest.NewRequest(
http.MethodGet,
"/reports/expense?export=excel&type=all&page=9&limit=1&search=operasional&category=BOP&supplier_id=7&kandang_id=4&project_flock_kandang_id=2&nonstock_id=5&area_id=3&location_id=9&realization_date=2026-04-22",
nil,
)
resp, err := app.Test(req)
if err != nil {
t.Fatalf("unexpected app.Test error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != fiber.StatusOK {
t.Fatalf("expected status 200, got %d", resp.StatusCode)
}
contentType := resp.Header.Get("Content-Type")
if contentType != "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" {
t.Fatalf("unexpected content-type: %s", contentType)
}
disposition := resp.Header.Get("Content-Disposition")
if !bytes.Contains([]byte(disposition), []byte("reports_expense_all_")) {
t.Fatalf("unexpected content-disposition: %s", disposition)
}
if len(stub.getExpenseCalls) != 2 {
t.Fatalf("expected 2 GetExpense calls, got %d", len(stub.getExpenseCalls))
}
firstCall := stub.getExpenseCalls[0]
secondCall := stub.getExpenseCalls[1]
if firstCall.Page != 1 || secondCall.Page != 2 {
t.Fatalf("expected internal pages 1 and 2, got %d and %d", firstCall.Page, secondCall.Page)
}
if firstCall.Limit != expenseReportExcelExportFetchLimit || secondCall.Limit != expenseReportExcelExportFetchLimit {
t.Fatalf("expected internal limit %d, got %d and %d", expenseReportExcelExportFetchLimit, firstCall.Limit, secondCall.Limit)
}
if firstCall.Search != "operasional" ||
firstCall.Category != "BOP" ||
firstCall.SupplierId != 7 ||
firstCall.KandangId != 4 ||
firstCall.ProjectFlockKandangId != 2 ||
firstCall.NonstockId != 5 ||
firstCall.AreaId != 3 ||
firstCall.LocationId != 9 ||
firstCall.RealizationDate != "2026-04-22" {
t.Fatalf("unexpected forwarded filters: %+v", firstCall)
}
payload, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("failed to read excel payload: %v", err)
}
file, err := excelize.OpenReader(bytes.NewReader(payload))
if err != nil {
t.Fatalf("failed to parse excel payload: %v", err)
}
defer file.Close()
sheetList := file.GetSheetList()
expectedSheets := []string{"EKSPEDISI ADE", "EKSPEDISI LTI"}
if !reflect.DeepEqual(sheetList, expectedSheets) {
t.Fatalf("unexpected sheet list: got %v, expected %v", sheetList, expectedSheets)
}
if got, _ := file.GetCellValue("EKSPEDISI ADE", "A1"); got != "No" {
t.Fatalf("expected EKSPEDISI ADE A1 to be No, got %q", got)
}
if got, _ := file.GetCellValue("EKSPEDISI ADE", "G2"); got != "TRANSPORT 2" {
t.Fatalf("expected EKSPEDISI ADE G2 to be TRANSPORT 2, got %q", got)
}
if got, _ := file.GetCellValue("EKSPEDISI LTI", "G2"); got != "TRANSPORT" {
t.Fatalf("expected EKSPEDISI LTI G2 to be TRANSPORT, got %q", got)
}
if got, _ := file.GetCellValue("EKSPEDISI LTI", "A4"); got != "Total" {
t.Fatalf("expected EKSPEDISI LTI A4 to be Total, got %q", got)
}
}
func TestRepportControllerGetExpenseKeepsPaginationValidationForNonExportAll(t *testing.T) {
app := fiber.New()
stub := &repportServiceStub{}
ctrl := NewRepportController(stub)
app.Get("/reports/expense", ctrl.GetExpense)
req := httptest.NewRequest(http.MethodGet, "/reports/expense?page=1&limit=0", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf("unexpected app.Test error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != fiber.StatusBadRequest {
t.Fatalf("expected status 400, got %d", resp.StatusCode)
}
}
func buildExpenseListForControllerTest(reference, product string) dto.RepportExpenseListDTO {
realizationDate := time.Date(2026, time.April, 22, 0, 0, 0, 0, time.UTC)
return dto.RepportExpenseListDTO{
RepportExpenseBaseDTO: dto.RepportExpenseBaseDTO{
ReferenceNumber: reference,
PoNumber: "PO-001",
Category: "BOP",
Notes: "catatan expense",
TransactionDate: time.Date(2026, time.April, 22, 0, 0, 0, 0, time.UTC),
RealizationDate: &realizationDate,
Supplier: &supplierDTO.SupplierRelationDTO{
Name: "Supplier A",
},
},
Kandang: &kandangDTO.KandangRelationDTO{
Name: "Kandang A",
Location: &locationDTO.LocationRelationDTO{
Name: "Darawati",
},
},
Pengajuan: dto.RepportExpensePengajuanDTO{
Qty: 1,
Price: 50000,
Notes: "catatan pengajuan",
Nonstock: &nonstockDTO.NonstockRelationDTO{
Name: product,
},
},
Realisasi: dto.RepportExpenseRealisasiDTO{
Qty: 1,
Price: 50000,
Notes: "catatan realisasi",
Nonstock: &nonstockDTO.NonstockRelationDTO{
Name: product,
},
},
TotalPengajuan: 50000,
TotalRealisasi: 50000,
LatestApproval: &approvalDTO.ApprovalRelationDTO{
StepName: "Realisasi",
},
}
}