mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
add export excel from api
This commit is contained in:
@@ -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",
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user