package controller import ( "math" "strconv" "strings" m "gitlab.com/mbugroup/lti-api.git/internal/middleware" "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" "gitlab.com/mbugroup/lti-api.git/internal/response" "github.com/gofiber/fiber/v2" ) // === Marketing Report Response === type MarketingReportResponse struct { Code int `json:"code"` Status string `json:"status"` Message string `json:"message"` Meta response.Meta `json:"meta"` Data []dto.RepportMarketingItemDTO `json:"data"` Total *dto.Summary `json:"total,omitempty"` } type RepportController struct { RepportService service.RepportService } func NewRepportController(repportService service.RepportService) *RepportController { return &RepportController{ RepportService: repportService, } } func (c *RepportController) GetExpense(ctx *fiber.Ctx) error { query := &validation.ExpenseQuery{ 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", ""), } locationScope, err := m.ResolveLocationScope(ctx, c.RepportService.DB()) if err != nil { return err } areaScope, err := m.ResolveAreaScope(ctx, c.RepportService.DB()) if err != nil { return err } if locationScope.Restrict { query.AllowedLocationIDs = toInt64Slice(locationScope.IDs) } if areaScope.Restrict { query.AllowedAreaIDs = toInt64Slice(areaScope.IDs) } 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.GetExpense(ctx, query) if err != nil { return err } return ctx.Status(fiber.StatusOK). JSON(response.SuccessWithPaginate[dto.RepportExpenseListDTO]{ Code: fiber.StatusOK, Status: "success", Message: "Get expense report successfully", Meta: response.Meta{ Page: query.Page, Limit: query.Limit, TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), TotalResults: totalResults, }, Data: result, }) } func (c *RepportController) GetMarketing(ctx *fiber.Ctx) error { query := &validation.MarketingQuery{ Page: ctx.QueryInt("page", 1), Limit: ctx.QueryInt("limit", 10), Search: ctx.Query("search", ""), CustomerId: int64(ctx.QueryInt("customer_id", 0)), ProductId: int64(ctx.QueryInt("product_id", 0)), WarehouseId: int64(ctx.QueryInt("warehouse_id", 0)), SalesPersonId: int64(ctx.QueryInt("sales_person_id", 0)), AreaId: int64(ctx.QueryInt("area_id", 0)), LocationId: int64(ctx.QueryInt("location_id", 0)), MarketingType: ctx.Query("marketing_type", ""), FilterBy: ctx.Query("filter_by", ""), StartDate: ctx.Query("start_date", ""), EndDate: ctx.Query("end_date", ""), SortBy: ctx.Query("sort_by", ""), SortOrder: ctx.Query("sort_order", ""), } locationScope, err := m.ResolveLocationScope(ctx, c.RepportService.DB()) if err != nil { return err } areaScope, err := m.ResolveAreaScope(ctx, c.RepportService.DB()) if err != nil { return err } if locationScope.Restrict { allowed := toInt64Slice(locationScope.IDs) if len(allowed) == 0 { allowed = []int64{-1} } query.AllowedLocationIDs = allowed } if areaScope.Restrict { allowed := toInt64Slice(areaScope.IDs) if len(allowed) == 0 { allowed = []int64{-1} } query.AllowedAreaIDs = allowed } 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.GetMarketing(ctx, query) if err != nil { return err } total := dto.ToSummaryFromDTOItems(result) return ctx.Status(fiber.StatusOK). JSON(MarketingReportResponse{ Code: fiber.StatusOK, Status: "success", Message: "Get marketing report successfully", Meta: response.Meta{ Page: query.Page, Limit: query.Limit, TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), TotalResults: totalResults, }, Data: result, Total: total, }) } func (c *RepportController) GetPurchaseSupplier(ctx *fiber.Ctx) error { query := &validation.PurchaseSupplierQuery{ Page: ctx.QueryInt("page", 1), Limit: ctx.QueryInt("limit", 10), AreaId: int64(ctx.QueryInt("area_id", 0)), SupplierId: int64(ctx.QueryInt("supplier_id", 0)), ProductId: int64(ctx.QueryInt("product_id", 0)), ProductCategoryId: int64(ctx.QueryInt("product_category_id", 0)), StartDate: ctx.Query("start_date", ""), EndDate: ctx.Query("end_date", ""), SortBy: ctx.Query("sort_by", ""), FilterBy: ctx.Query("filter_by", ""), } areaScope, err := m.ResolveAreaScope(ctx, c.RepportService.DB()) if err != nil { return err } if areaScope.Restrict { query.AllowedAreaIDs = toInt64Slice(areaScope.IDs) } 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.GetPurchaseSupplier(ctx, query) if err != nil { return err } filters := map[string]interface{}{ "area_id": query.AreaId, "supplier_id": query.SupplierId, "product_id": query.ProductId, "product_category_id": query.ProductCategoryId, "start_date": query.StartDate, "end_date": query.EndDate, "sort_by": query.SortBy, "filter_by": query.FilterBy, } return ctx.Status(fiber.StatusOK). JSON(response.SuccessWithPaginate[dto.PurchaseSupplierDTO]{ Code: fiber.StatusOK, Status: "success", Message: "Get supplier purchase recap successfully", Meta: response.Meta{ Page: query.Page, Limit: query.Limit, TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), TotalResults: totalResults, Filters: filters, }, Data: result, }) } func (c *RepportController) GetDebtSupplier(ctx *fiber.Ctx) error { supplierIDs, err := parseCommaSeparatedInt64s(ctx.Query("supplier_ids", "")) if err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } query := &validation.DebtSupplierQuery{ Page: ctx.QueryInt("page", 1), Limit: ctx.QueryInt("limit", 10), SupplierIDs: supplierIDs, StartDate: ctx.Query("start_date", ""), EndDate: ctx.Query("end_date", ""), FilterBy: ctx.Query("filter_by", ""), SortOrder: ctx.Query("sort_order", ""), } locationScope, err := m.ResolveLocationScope(ctx, c.RepportService.DB()) if err != nil { return err } areaScope, err := m.ResolveAreaScope(ctx, c.RepportService.DB()) if err != nil { return err } if locationScope.Restrict { query.AllowedLocationIDs = toInt64Slice(locationScope.IDs) } if areaScope.Restrict { query.AllowedAreaIDs = toInt64Slice(areaScope.IDs) } 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.GetDebtSupplier(ctx, query) if err != nil { return err } supplierIDs = query.SupplierIDs if supplierIDs == nil { supplierIDs = []int64{} } filters := map[string]interface{}{ "start_date": query.StartDate, "end_date": query.EndDate, "supplier_ids": supplierIDs, "filter_by": query.FilterBy, } return ctx.Status(fiber.StatusOK). JSON(response.SuccessWithPaginate[dto.DebtSupplierDTO]{ Code: fiber.StatusOK, Status: "success", Message: "Get supplier debt recap successfully", Meta: response.Meta{ Page: query.Page, Limit: query.Limit, TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), TotalResults: totalResults, Filters: filters, }, Data: result, }) } func (c *RepportController) GetHppPerKandang(ctx *fiber.Ctx) error { data, meta, err := c.RepportService.GetHppPerKandang(ctx) if err != nil { return err } resp := struct { Code int `json:"code"` Status string `json:"status"` Message string `json:"message"` Meta dto.HppPerKandangMetaDTO `json:"meta"` Data dto.HppPerKandangResponseData `json:"data"` }{ Code: fiber.StatusOK, Status: "success", Message: "Get HPP harian kandang layer successfully", Meta: *meta, Data: *data, } return ctx.Status(fiber.StatusOK).JSON(resp) } func (c *RepportController) GetCustomerPayment(ctx *fiber.Ctx) error { var customerIDs []uint if customerIDsStr := ctx.Query("customer_ids"); customerIDsStr != "" { ids := strings.Split(customerIDsStr, ",") for _, idStr := range ids { idStr = strings.TrimSpace(idStr) if idStr != "" { if id, err := strconv.ParseUint(idStr, 10, 32); err == nil { customerIDs = append(customerIDs, uint(id)) } } } } query := &validation.CustomerPaymentQuery{ Page: ctx.QueryInt("page", 1), Limit: ctx.QueryInt("limit", 10), CustomerIDs: customerIDs, StartDate: ctx.Query("start_date", ""), EndDate: ctx.Query("end_date", ""), } // Validate pagination if len(customerIDs) == 0 && (query.Page < 1 || query.Limit < 1) { return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0 when customer_ids is not provided") } result, totalResults, err := c.RepportService.GetCustomerPayment(ctx, query) if err != nil { return err } // If single customer mode (only 1 customer ID), return without pagination if len(customerIDs) == 1 { return ctx.Status(fiber.StatusOK). JSON(response.Success{ Code: fiber.StatusOK, Status: "success", Message: "Get customer payment report successfully", Data: result, }) } // Multiple customers mode with pagination return ctx.Status(fiber.StatusOK). JSON(response.SuccessWithPaginate[dto.CustomerPaymentReportItem]{ Code: fiber.StatusOK, Status: "success", Message: "Get customer payment report successfully", Meta: response.Meta{ Page: query.Page, Limit: query.Limit, TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), TotalResults: totalResults, }, Data: result, }) } func (c *RepportController) GetProductionResult(ctx *fiber.Ctx) error { idParam := ctx.Params("idProjectFlockKandang") if idParam == "" { return fiber.NewError(fiber.StatusBadRequest, "idProjectFlockKandang is required") } projectFlockKandangID, err := strconv.ParseUint(idParam, 10, 64) if err != nil { return fiber.NewError(fiber.StatusBadRequest, "invalid idProjectFlockKandang") } query := &validation.ProductionResultQuery{ Page: ctx.QueryInt("page", 1), Limit: ctx.QueryInt("limit", 10), ProjectFlockKandangID: uint(projectFlockKandangID), } if err := m.EnsureProjectFlockKandangAccess(ctx, c.RepportService.DB(), 0, query.ProjectFlockKandangID); err != nil { return err } if query.Page < 1 || query.Limit < 1 { return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") } data, totalResults, err := c.RepportService.GetProductionResult(ctx, query) if err != nil { return err } return ctx.Status(fiber.StatusOK). JSON(response.SuccessWithPaginate[dto.ProductionResultDTO]{ Code: fiber.StatusOK, Status: "success", Message: "Get Laporan Hasil Produksi successfully", Meta: response.Meta{ Page: query.Page, Limit: query.Limit, TotalPages: int64(math.Ceil(float64(totalResults) / float64(query.Limit))), TotalResults: totalResults, }, Data: data, }) } func parseCommaSeparatedInt64s(raw string) ([]int64, error) { raw = strings.TrimSpace(raw) if raw == "" { return []int64{}, nil } parts := strings.Split(raw, ",") result := make([]int64, 0, len(parts)) for _, part := range parts { part = strings.TrimSpace(part) if part == "" { continue } id, err := strconv.ParseInt(part, 10, 64) if err != nil { return nil, fiber.NewError(fiber.StatusBadRequest, "supplier_ids must be comma separated integers") } result = append(result, id) } return result, nil } func toInt64Slice(ids []uint) []int64 { if len(ids) == 0 { return nil } out := make([]int64, 0, len(ids)) for _, id := range ids { out = append(out, int64(id)) } return out }