From cbb3368141fa8467c9cb711d9f8ce224f94d9dc5 Mon Sep 17 00:00:00 2001 From: aguhh18 Date: Mon, 15 Dec 2025 09:11:26 +0700 Subject: [PATCH] FEAT[BE]: implement expense report retrieval with filtering options --- .../closings/dto/closingMarketing.dto.go | 13 +- .../expense_realization.repository.go | 83 +++++++++ .../services/adjustment.service.go | 3 +- .../product_warehouse.repository.go | 2 +- .../controllers/projectflock.controller.go | 3 +- .../controllers/repport.controller.go | 67 ++----- internal/modules/repports/dto/repport.dto.go | 16 -- .../repports/dto/repportExpense.dto.go | 173 ++++++++++++++++++ internal/modules/repports/module.go | 11 +- internal/modules/repports/route.go | 4 - .../repports/services/repport.service.go | 98 ++++------ .../validations/repport.validation.go | 15 +- 12 files changed, 330 insertions(+), 158 deletions(-) delete mode 100644 internal/modules/repports/dto/repport.dto.go create mode 100644 internal/modules/repports/dto/repportExpense.dto.go diff --git a/internal/modules/closings/dto/closingMarketing.dto.go b/internal/modules/closings/dto/closingMarketing.dto.go index a442fc9d..ea0ddb81 100644 --- a/internal/modules/closings/dto/closingMarketing.dto.go +++ b/internal/modules/closings/dto/closingMarketing.dto.go @@ -28,10 +28,7 @@ type SalesDTO struct { } type PenjualanRealisasiResponseDTO struct { - ProjectType string `json:"project_type"` - FlockId uint `json:"flock_id"` - Period int `json:"period"` - Sales []SalesDTO `json:"sales"` + Sales []SalesDTO `json:"sales"` } // === Mapper Functions === @@ -87,12 +84,10 @@ func ToSalesDTOs(e []entity.MarketingDeliveryProduct) []SalesDTO { } func ToPenjualanRealisasiResponseDTO(projectType string, projectFlockID uint, e []entity.MarketingDeliveryProduct) PenjualanRealisasiResponseDTO { - period := extractPeriodFromRealisasi(e) + return PenjualanRealisasiResponseDTO{ - ProjectType: projectType, - FlockId: projectFlockID, - Period: period, - Sales: ToSalesDTOs(e), + + Sales: ToSalesDTOs(e), } } diff --git a/internal/modules/expenses/repositories/expense_realization.repository.go b/internal/modules/expenses/repositories/expense_realization.repository.go index e60324ca..592c8d27 100644 --- a/internal/modules/expenses/repositories/expense_realization.repository.go +++ b/internal/modules/expenses/repositories/expense_realization.repository.go @@ -5,6 +5,8 @@ import ( "gitlab.com/mbugroup/lti-api.git/internal/common/repository" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/validations" + "gitlab.com/mbugroup/lti-api.git/internal/utils" "gorm.io/gorm" ) @@ -13,6 +15,7 @@ type ExpenseRealizationRepository interface { IdExists(ctx context.Context, id uint64) (bool, error) GetByExpenseNonstockID(ctx context.Context, expenseNonstockID uint64) (*entity.ExpenseRealization, error) GetByProjectFlockID(ctx context.Context, projectFlockID uint) ([]entity.ExpenseRealization, error) + GetAllWithFilters(ctx context.Context, offset, limit int, filters *validation.Query) ([]entity.ExpenseRealization, int64, error) } type ExpenseRealizationRepositoryImpl struct { @@ -50,3 +53,83 @@ func (r *ExpenseRealizationRepositoryImpl) GetByProjectFlockID(ctx context.Conte Find(&realizations).Error return realizations, err } + +func (r *ExpenseRealizationRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, filters *validation.Query) ([]entity.ExpenseRealization, int64, error) { + var realizations []entity.ExpenseRealization + var total int64 + + db := r.DB().WithContext(ctx). + Model(&entity.ExpenseRealization{}). + Preload("ExpenseNonstock", func(db *gorm.DB) *gorm.DB { + return db. + Preload("Expense"). + Preload("Expense.Supplier"). + Preload("Kandang"). + Preload("Kandang.Location"). + Preload("Nonstock") + }). + Joins("JOIN expense_nonstocks ON expense_nonstocks.id = expense_realizations.expense_nonstock_id"). + Joins("JOIN expenses ON expenses.id = expense_nonstocks.expense_id"). + Joins("LEFT JOIN suppliers ON suppliers.id = expenses.supplier_id") + + if filters.Search != "" { + db = db.Where("expenses.category LIKE ? OR expenses.reference_number LIKE ? OR expenses.po_number LIKE ? OR expenses.notes LIKE ? OR suppliers.name LIKE ?", + "%"+filters.Search+"%", "%"+filters.Search+"%", "%"+filters.Search+"%", "%"+filters.Search+"%", "%"+filters.Search+"%") + } + + if filters.Category != "" { + db = db.Where("expenses.category = ?", filters.Category) + } + + if filters.SupplierId > 0 { + db = db.Where("expenses.supplier_id = ?", filters.SupplierId) + } + + if filters.KandangId > 0 { + db = db.Where("expense_nonstocks.kandang_id = ?", filters.KandangId) + } + + if filters.ProjectFlockKandangId > 0 { + db = db.Where("expense_nonstocks.project_flock_kandang_id = ?", filters.ProjectFlockKandangId) + } + + if filters.NonstockId > 0 { + db = db.Where("expense_nonstocks.nonstock_id = ?", filters.NonstockId) + } + + locationID := filters.LocationId + areaID := filters.AreaId + + if locationID > 0 || areaID > 0 { + db = db.Joins("JOIN kandangs ON kandangs.id = expense_nonstocks.kandang_id") + + if locationID > 0 { + db = db.Where("kandangs.location_id = ?", uint(locationID)) + } + + if areaID > 0 { + db = db.Joins("JOIN locations ON locations.id = kandangs.location_id"). + Where("locations.area_id = ?", uint(areaID)) + } + } + + if filters.RealizationDate != "" { + if realizationDate, err := utils.ParseDateString(filters.RealizationDate); err == nil { + db = db.Where("DATE(expenses.realization_date) = ?", realizationDate) + } + } + + if err := db.Count(&total).Error; err != nil { + return nil, 0, err + } + + if err := db. + Offset(offset). + Limit(limit). + Order("expense_realizations.created_at DESC"). + Find(&realizations).Error; err != nil { + return nil, 0, err + } + + return realizations, total, nil +} diff --git a/internal/modules/inventory/adjustments/services/adjustment.service.go b/internal/modules/inventory/adjustments/services/adjustment.service.go index da118438..7bcbca7e 100644 --- a/internal/modules/inventory/adjustments/services/adjustment.service.go +++ b/internal/modules/inventory/adjustments/services/adjustment.service.go @@ -141,7 +141,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e if err := common.EnsureProjectFlockNotClosedForProductWarehouses( ctx, s.StockLogsRepository.DB(), - []uint{pw.Id}, + []uint{pw.Id}, ); err != nil { return nil, err } @@ -229,7 +229,6 @@ func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Qu isWarehousesExist, err := s.WarehouseRepo.IdExists(c.Context(), uint(query.WarehouseID)) if err != nil { - s.Log.Errorf("Failed to check warehouse existence: %+v", err) return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate warehouse") } if query.WarehouseID > 0 && !isWarehousesExist { diff --git a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go index ee92f6ab..2fc8bc3d 100644 --- a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go +++ b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go @@ -115,7 +115,7 @@ func (r *ProductWarehouseRepositoryImpl) GetLatestByCategoryCodeAndWarehouseID(c Joins("JOIN products ON products.id = product_warehouses.product_id"). Joins("JOIN product_categories ON product_categories.id = products.product_category_id"). Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", categoryCode, warehouseId). - Order("product_warehouses.id DESC"). + Order("product_warehouses.created_at DESC"). Preload("Product").Preload("Warehouse"). First(&productWarehouse).Error if err != nil { diff --git a/internal/modules/production/project_flocks/controllers/projectflock.controller.go b/internal/modules/production/project_flocks/controllers/projectflock.controller.go index 937c9058..c48e1e2a 100644 --- a/internal/modules/production/project_flocks/controllers/projectflock.controller.go +++ b/internal/modules/production/project_flocks/controllers/projectflock.controller.go @@ -281,7 +281,6 @@ func (u *ProjectflockController) LookupProjectFlockKandang(c *fiber.Ctx) error { dtoResult := dto.ToProjectFlockKandangDTO(*result) dtoResult.AvailableQuantity = float64(availableStock) - // populate available quantity for each kandang inside project_flock if dtoResult.ProjectFlock != nil { for i := range dtoResult.ProjectFlock.Kandangs { kand := &dtoResult.ProjectFlock.Kandangs[i] @@ -292,7 +291,7 @@ func (u *ProjectflockController) LookupProjectFlockKandang(c *fiber.Ctx) error { kand.AvailableQuantity = q } } - // remove inner kandangs from project_flock to avoid duplication + dtoResult.ProjectFlock.Kandangs = nil } diff --git a/internal/modules/repports/controllers/repport.controller.go b/internal/modules/repports/controllers/repport.controller.go index e4b6088e..11563f7f 100644 --- a/internal/modules/repports/controllers/repport.controller.go +++ b/internal/modules/repports/controllers/repport.controller.go @@ -2,7 +2,6 @@ package controller import ( "math" - "strconv" "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto" service "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/services" @@ -22,27 +21,35 @@ func NewRepportController(repportService service.RepportService) *RepportControl } } -func (c *RepportController) GetAll(ctx *fiber.Ctx) error { +func (c *RepportController) GetExpense(ctx *fiber.Ctx) error { query := &validation.Query{ - Page: ctx.QueryInt("page", 1), - Limit: ctx.QueryInt("limit", 10), - Search: ctx.Query("search", ""), + Page: ctx.QueryInt("page", 1), + Limit: ctx.QueryInt("limit", 10), + Search: ctx.Query("search", ""), + Category: ctx.Query("category", ""), + SupplierId: int64(ctx.QueryInt("supplier_id", 0)), + KandangId: int64(ctx.QueryInt("kandang_id", 0)), + ProjectFlockKandangId: int64(ctx.QueryInt("project_flock_kandang_id", 0)), + NonstockId: int64(ctx.QueryInt("nonstock_id", 0)), + AreaId: int64(ctx.QueryInt("area_id", 0)), + LocationId: int64(ctx.QueryInt("location_id", 0)), + RealizationDate: ctx.Query("realization_date", ""), } if query.Page < 1 || query.Limit < 1 { return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") } - result, totalResults, err := c.RepportService.GetAll(ctx, query) + result, totalResults, err := c.RepportService.GetExpense(ctx, query) if err != nil { return err } return ctx.Status(fiber.StatusOK). - JSON(response.SuccessWithPaginate[dto.RepportListDTO]{ + JSON(response.SuccessWithPaginate[dto.RepportExpenseListDTO]{ Code: fiber.StatusOK, Status: "success", - Message: "Get all reports successfully", + Message: "Get expense report successfully", Meta: response.Meta{ Page: query.Page, Limit: query.Limit, @@ -52,47 +59,3 @@ func (c *RepportController) GetAll(ctx *fiber.Ctx) error { Data: result, }) } - -func (c *RepportController) GetOne(ctx *fiber.Ctx) error { - param := ctx.Params("id") - - id, err := strconv.Atoi(param) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") - } - - result, err := c.RepportService.GetOne(ctx, uint(id)) - if err != nil { - return err - } - - return ctx.Status(fiber.StatusOK). - JSON(response.Success{ - Code: fiber.StatusOK, - Status: "success", - Message: "Get report successfully", - Data: result, - }) -} - -func (c *RepportController) GetExpense(ctx *fiber.Ctx) error { - param := ctx.Params("id") - - id, err := strconv.Atoi(param) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid Id") - } - - result, err := c.RepportService.GetOne(ctx, uint(id)) - if err != nil { - return err - } - - return ctx.Status(fiber.StatusOK). - JSON(response.Success{ - Code: fiber.StatusOK, - Status: "success", - Message: "Get report successfully", - Data: result, - }) -} diff --git a/internal/modules/repports/dto/repport.dto.go b/internal/modules/repports/dto/repport.dto.go deleted file mode 100644 index 154c6f47..00000000 --- a/internal/modules/repports/dto/repport.dto.go +++ /dev/null @@ -1,16 +0,0 @@ -package dto - -import "time" - -// === DTO Structs === - -type RepportListDTO struct { - Id uint `json:"id"` - Name string `json:"name"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -type RepportDetailDTO struct { - RepportListDTO -} diff --git a/internal/modules/repports/dto/repportExpense.dto.go b/internal/modules/repports/dto/repportExpense.dto.go new file mode 100644 index 00000000..1a17dd5b --- /dev/null +++ b/internal/modules/repports/dto/repportExpense.dto.go @@ -0,0 +1,173 @@ +package dto + +import ( + "time" + + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto" + kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/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" +) + +// === DTO Structs === + +type RepportExpenseBaseDTO struct { + Id uint64 `json:"id"` + ReferenceNumber string `json:"reference_number"` + PoNumber string `json:"po_number"` + Category string `json:"category"` + Supplier *supplierDTO.SupplierRelationDTO `json:"supplier,omitempty"` + RealizationDate *time.Time `json:"realization_date,omitempty"` + TransactionDate time.Time `json:"transaction_date"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type RepportExpensePengajuanDTO struct { + Id uint64 `json:"id"` + ExpenseId *uint64 `json:"expense_id,omitempty"` + ProjectFlockKandangId *uint64 `json:"project_flock_kandang_id,omitempty"` + Qty float64 `json:"qty"` + Price float64 `json:"price"` + Notes string `json:"notes"` + Nonstock *nonstockDTO.NonstockRelationDTO `json:"nonstock,omitempty"` + Kandang *kandangDTO.KandangRelationDTO `json:"kandang,omitempty"` + CreatedAt time.Time `json:"created_at"` +} + +type RepportExpenseRealisasiDTO struct { + Id *uint64 `json:"id,omitempty"` + ExpenseNonstockId *uint64 `json:"expense_nonstock_id,omitempty"` + Qty float64 `json:"qty"` + Price float64 `json:"price"` + Notes string `json:"notes"` + Nonstock *nonstockDTO.NonstockRelationDTO `json:"nonstock,omitempty"` + CreatedAt time.Time `json:"created_at"` +} + +type RepportExpenseListDTO struct { + RepportExpenseBaseDTO + Pengajuan RepportExpensePengajuanDTO `json:"pengajuan"` + Realisasi RepportExpenseRealisasiDTO `json:"realisasi"` + TotalPengajuan float64 `json:"total_pengajuan"` + TotalRealisasi float64 `json:"total_realisasi"` + LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval,omitempty"` +} + +// === MAPPERS === + +func ToRepportExpenseBaseDTO(e *entity.Expense) RepportExpenseBaseDTO { + var realizationDate *time.Time + if !e.RealizationDate.IsZero() { + realizationDate = &e.RealizationDate + } + + var supplier *supplierDTO.SupplierRelationDTO + if e.Supplier != nil && e.Supplier.Id != 0 { + mapped := supplierDTO.ToSupplierRelationDTO(*e.Supplier) + supplier = &mapped + } + + return RepportExpenseBaseDTO{ + Id: e.Id, + ReferenceNumber: e.ReferenceNumber, + PoNumber: e.PoNumber, + Category: e.Category, + Supplier: supplier, + RealizationDate: realizationDate, + TransactionDate: e.TransactionDate, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + } +} + +func ToRepportExpensePengajuanDTO(ns *entity.ExpenseNonstock) RepportExpensePengajuanDTO { + var nonstock *nonstockDTO.NonstockRelationDTO + if ns.Nonstock != nil && ns.Nonstock.Id != 0 { + mapped := nonstockDTO.ToNonstockRelationDTO(*ns.Nonstock) + nonstock = &mapped + } + + var kandang *kandangDTO.KandangRelationDTO + if ns.Kandang != nil && ns.Kandang.Id != 0 { + mapped := kandangDTO.ToKandangRelationDTO(*ns.Kandang) + kandang = &mapped + } + + return RepportExpensePengajuanDTO{ + Id: ns.Id, + ExpenseId: ns.ExpenseId, + ProjectFlockKandangId: ns.ProjectFlockKandangId, + Qty: ns.Qty, + Price: ns.Price, + Notes: ns.Notes, + Nonstock: nonstock, + Kandang: kandang, + CreatedAt: ns.CreatedAt, + } +} + +func ToRepportExpenseRealisasiDTO(r *entity.ExpenseRealization) RepportExpenseRealisasiDTO { + var nonstock *nonstockDTO.NonstockRelationDTO + if r.ExpenseNonstock != nil && r.ExpenseNonstock.Nonstock != nil && r.ExpenseNonstock.Nonstock.Id != 0 { + mapped := nonstockDTO.ToNonstockRelationDTO(*r.ExpenseNonstock.Nonstock) + nonstock = &mapped + } + + return RepportExpenseRealisasiDTO{ + Id: r.ExpenseNonstockId, + ExpenseNonstockId: r.ExpenseNonstockId, + Qty: r.Qty, + Price: r.Price, + Notes: r.Notes, + Nonstock: nonstock, + CreatedAt: r.CreatedAt, + } +} + +func ToRepportExpenseListDTO(baseDTO RepportExpenseBaseDTO, ns *entity.ExpenseNonstock, latestApproval *approvalDTO.ApprovalRelationDTO) RepportExpenseListDTO { + var realisasi RepportExpenseRealisasiDTO + if ns.Realization != nil { + realisasi = ToRepportExpenseRealisasiDTO(ns.Realization) + } + + totalPengajuan := ns.Qty * ns.Price + totalRealisasi := float64(0) + if ns.Realization != nil { + totalRealisasi = ns.Realization.Qty * ns.Realization.Price + } + + return RepportExpenseListDTO{ + RepportExpenseBaseDTO: baseDTO, + Pengajuan: ToRepportExpensePengajuanDTO(ns), + Realisasi: realisasi, + TotalPengajuan: totalPengajuan, + TotalRealisasi: totalRealisasi, + LatestApproval: latestApproval, + } +} + +func ToRepportExpenseListDTOs(realizations []entity.ExpenseRealization) []RepportExpenseListDTO { + result := make([]RepportExpenseListDTO, 0, len(realizations)) + + for _, realization := range realizations { + if realization.ExpenseNonstock == nil || realization.ExpenseNonstock.Expense == nil { + continue + } + + expense := realization.ExpenseNonstock.Expense + baseDTO := ToRepportExpenseBaseDTO(expense) + + var latestApproval *approvalDTO.ApprovalRelationDTO + if expense.LatestApproval != nil { + mapped := approvalDTO.ToApprovalDTO(*expense.LatestApproval) + latestApproval = &mapped + } + + dto := ToRepportExpenseListDTO(baseDTO, realization.ExpenseNonstock, latestApproval) + result = append(result, dto) + } + + return result +} diff --git a/internal/modules/repports/module.go b/internal/modules/repports/module.go index be0ba7a3..108b0a1b 100644 --- a/internal/modules/repports/module.go +++ b/internal/modules/repports/module.go @@ -5,6 +5,8 @@ import ( "github.com/gofiber/fiber/v2" "gorm.io/gorm" + commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + approvalService "gitlab.com/mbugroup/lti-api.git/internal/common/service" sRepport "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/services" expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories" @@ -13,11 +15,12 @@ import ( type RepportModule struct{} func (RepportModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) { - // Initialize expense realization repository - expRealizationRepo := expenseRepo.NewExpenseRealizationRepository(db) - // Initialize report service with expense realization repo - repportService := sRepport.NewRepportService(validate, expRealizationRepo) + expenseRealizationRepository := expenseRepo.NewExpenseRealizationRepository(db) + approvalRepository := commonRepo.NewApprovalRepository(db) + + approvalSvc := approvalService.NewApprovalService(approvalRepository) + repportService := sRepport.NewRepportService(validate, expenseRealizationRepository, approvalSvc) RepportRoutes(router, repportService) } diff --git a/internal/modules/repports/route.go b/internal/modules/repports/route.go index d01fd4b2..b312174e 100644 --- a/internal/modules/repports/route.go +++ b/internal/modules/repports/route.go @@ -1,7 +1,6 @@ package repports import ( - controller "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/controllers" repport "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/services" @@ -13,8 +12,5 @@ func RepportRoutes(v1 fiber.Router, s repport.RepportService) { route := v1.Group("/repports") - route.Get("/", ctrl.GetAll) - route.Get("/:id", ctrl.GetOne) - route.Get("expense", ctrl.GetExpense) } diff --git a/internal/modules/repports/services/repport.service.go b/internal/modules/repports/services/repport.service.go index 82fd5470..15f2d635 100644 --- a/internal/modules/repports/services/repport.service.go +++ b/internal/modules/repports/services/repport.service.go @@ -1,106 +1,74 @@ package service import ( - "strings" - "time" - "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/dto" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/repports/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" + approvalService "gitlab.com/mbugroup/lti-api.git/internal/common/service" + approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/dto" expenseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" "github.com/sirupsen/logrus" + "gorm.io/gorm" ) type RepportService interface { - GetAll(ctx *fiber.Ctx, params *validation.Query) ([]dto.RepportListDTO, int64, error) - GetOne(ctx *fiber.Ctx, id uint) (*dto.RepportListDTO, error) - GetExpense(ctx *fiber.Ctx, id uint) (*dto.RepportListDTO, error) + GetExpense(ctx *fiber.Ctx, params *validation.Query) ([]dto.RepportExpenseListDTO, int64, error) } type repportService struct { Log *logrus.Logger Validate *validator.Validate - dummyData map[uint]dto.RepportListDTO ExpenseRealizationRepo expenseRepo.ExpenseRealizationRepository + ApprovalSvc approvalService.ApprovalService } -func NewRepportService(validate *validator.Validate, expenseRealizationRepo expenseRepo.ExpenseRealizationRepository) RepportService { - // Initialize with dummy data - now := time.Now() - dummyData := map[uint]dto.RepportListDTO{ - 1: { - Id: 1, - Name: "Sales Report", - CreatedAt: now, - UpdatedAt: now, - }, - 2: { - Id: 2, - Name: "Inventory Report", - CreatedAt: now, - UpdatedAt: now, - }, - 3: { - Id: 3, - Name: "Production Report", - CreatedAt: now, - UpdatedAt: now, - }, - } - +func NewRepportService(validate *validator.Validate, expenseRealizationRepo expenseRepo.ExpenseRealizationRepository, approvalSvc approvalService.ApprovalService) RepportService { return &repportService{ Log: utils.Log, Validate: validate, - dummyData: dummyData, ExpenseRealizationRepo: expenseRealizationRepo, + ApprovalSvc: approvalSvc, } } -func (s *repportService) GetAll(c *fiber.Ctx, params *validation.Query) ([]dto.RepportListDTO, int64, error) { +func (s *repportService) GetExpense(c *fiber.Ctx, params *validation.Query) ([]dto.RepportExpenseListDTO, int64, error) { if err := s.Validate.Struct(params); err != nil { return nil, 0, err } - // Convert map to slice - var results []dto.RepportListDTO - for _, v := range s.dummyData { - // Apply search filter if provided - if params.Search != "" && !strings.Contains(strings.ToLower(v.Name), strings.ToLower(params.Search)) { - continue - } - results = append(results, v) - } - - // Apply pagination - total := int64(len(results)) offset := (params.Page - 1) * params.Limit - if offset >= int(total) { - return []dto.RepportListDTO{}, total, nil + realizations, total, err := s.ExpenseRealizationRepo.GetAllWithFilters(c.Context(), offset, params.Limit, params) + if err != nil { + s.Log.Errorf("GetAllWithFilters error: %v", err) + return nil, 0, err } - end := offset + params.Limit - if end > int(total) { - end = int(total) + result := dto.ToRepportExpenseListDTOs(realizations) + + expenseIDs := make([]uint, 0, len(result)) + for i := range result { + expenseIDs = append(expenseIDs, uint(result[i].Id)) } - return results[offset:end], total, nil -} - -func (s *repportService) GetOne(c *fiber.Ctx, id uint) (*dto.RepportListDTO, error) { - if data, ok := s.dummyData[id]; ok { - return &data, nil - } - return nil, fiber.NewError(fiber.StatusNotFound, "Report not found") -} - -func (s *repportService) GetExpense(c *fiber.Ctx, id uint) (*dto.RepportListDTO, error) { - if data, ok := s.dummyData[id]; ok { - return &data, nil - } - return nil, fiber.NewError(fiber.StatusNotFound, "Report not found") + approvals, err := s.ApprovalSvc.LatestByTargets(c.Context(), utils.ApprovalWorkflowExpense, expenseIDs, func(db *gorm.DB) *gorm.DB { + return db.Preload("ActionUser") + }) + if err != nil { + s.Log.Warnf("LatestByTargets error: %v", err) + } + + for i := range result { + expenseIDAsUint := uint(result[i].Id) + if approval, exists := approvals[expenseIDAsUint]; exists && approval != nil { + mapped := approvalDTO.ToApprovalDTO(*approval) + result[i].LatestApproval = &mapped + } + } + + return result, total, nil } diff --git a/internal/modules/repports/validations/repport.validation.go b/internal/modules/repports/validations/repport.validation.go index a7ec4a6d..3d0eb344 100644 --- a/internal/modules/repports/validations/repport.validation.go +++ b/internal/modules/repports/validations/repport.validation.go @@ -1,7 +1,16 @@ package validation type Query struct { - Page int `query:"page" validate:"omitempty,number,min=1,gt=0"` - Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"` - Search string `query:"search" validate:"omitempty,max=50"` + Page int `query:"page" validate:"omitempty,min=1,gt=0"` + Limit int `query:"limit" validate:"omitempty,min=1,max=100,gt=0"` + Search string `query:"search" validate:"omitempty,max=100"` + Category string `query:"category" validate:"omitempty,oneof=BOP NON-BOP"` + SupplierId int64 `query:"supplier_id" validate:"omitempty"` + KandangId int64 `query:"kandang_id" validate:"omitempty"` + ProjectFlockKandangId int64 `query:"project_flock_kandang_id" validate:"omitempty"` + ProjectFlockId int64 `query:"project_flock_id" validate:"omitempty"` + NonstockId int64 `query:"nonstock_id" validate:"omitempty"` + AreaId int64 `query:"area_id" validate:"omitempty"` + LocationId int64 `query:"location_id" validate:"omitempty"` + RealizationDate string `query:"realization_date" validate:"omitempty"` }