package handler import ( "apskel-pos-be/internal/appcontext" "apskel-pos-be/internal/util" "strconv" "apskel-pos-be/internal/constants" "apskel-pos-be/internal/contract" "apskel-pos-be/internal/logger" "apskel-pos-be/internal/service" "apskel-pos-be/internal/validator" "github.com/gin-gonic/gin" "github.com/google/uuid" ) type ExpenseHandler struct { expenseService service.ExpenseService expenseValidator validator.ExpenseValidator } func NewExpenseHandler( expenseService service.ExpenseService, expenseValidator validator.ExpenseValidator, ) *ExpenseHandler { return &ExpenseHandler{ expenseService: expenseService, expenseValidator: expenseValidator, } } func (h *ExpenseHandler) CreateExpense(c *gin.Context) { ctx := c.Request.Context() contextInfo := appcontext.FromGinContext(ctx) var req contract.CreateExpenseRequest if err := c.ShouldBindJSON(&req); err != nil { logger.FromContext(ctx).WithError(err).Error("ExpenseHandler::CreateExpense -> request binding failed") validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error()) util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "ExpenseHandler::CreateExpense") return } validationError, validationErrorCode := h.expenseValidator.ValidateCreateExpenseRequest(&req) if validationError != nil { validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error()) util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "ExpenseHandler::CreateExpense") return } expenseResponse := h.expenseService.CreateExpense(ctx, contextInfo, &req) if expenseResponse.HasErrors() { errorResp := expenseResponse.GetErrors()[0] logger.FromContext(ctx).WithError(errorResp).Error("ExpenseHandler::CreateExpense -> Failed to create expense from service") } util.HandleResponse(c.Writer, c.Request, expenseResponse, "ExpenseHandler::CreateExpense") } func (h *ExpenseHandler) UpdateExpense(c *gin.Context) { ctx := c.Request.Context() contextInfo := appcontext.FromGinContext(ctx) expenseIDStr := c.Param("id") expenseID, err := uuid.Parse(expenseIDStr) if err != nil { logger.FromContext(ctx).WithError(err).Error("ExpenseHandler::UpdateExpense -> Invalid expense ID") validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid expense ID") util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "ExpenseHandler::UpdateExpense") return } var req contract.UpdateExpenseRequest if err := c.ShouldBindJSON(&req); err != nil { logger.FromContext(ctx).WithError(err).Error("ExpenseHandler::UpdateExpense -> request binding failed") validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "Invalid request body") util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "ExpenseHandler::UpdateExpense") return } validationError, validationErrorCode := h.expenseValidator.ValidateUpdateExpenseRequest(&req) if validationError != nil { validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error()) util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "ExpenseHandler::UpdateExpense") return } expenseResponse := h.expenseService.UpdateExpense(ctx, contextInfo, expenseID, &req) if expenseResponse.HasErrors() { errorResp := expenseResponse.GetErrors()[0] logger.FromContext(ctx).WithError(errorResp).Error("ExpenseHandler::UpdateExpense -> Failed to update expense from service") } util.HandleResponse(c.Writer, c.Request, expenseResponse, "ExpenseHandler::UpdateExpense") } func (h *ExpenseHandler) DeleteExpense(c *gin.Context) { ctx := c.Request.Context() contextInfo := appcontext.FromGinContext(ctx) expenseIDStr := c.Param("id") expenseID, err := uuid.Parse(expenseIDStr) if err != nil { logger.FromContext(ctx).WithError(err).Error("ExpenseHandler::DeleteExpense -> Invalid expense ID") validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid expense ID") util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "ExpenseHandler::DeleteExpense") return } expenseResponse := h.expenseService.DeleteExpense(ctx, contextInfo, expenseID) if expenseResponse.HasErrors() { errorResp := expenseResponse.GetErrors()[0] logger.FromContext(ctx).WithError(errorResp).Error("ExpenseHandler::DeleteExpense -> Failed to delete expense from service") } util.HandleResponse(c.Writer, c.Request, expenseResponse, "ExpenseHandler::DeleteExpense") } func (h *ExpenseHandler) GetExpense(c *gin.Context) { ctx := c.Request.Context() contextInfo := appcontext.FromGinContext(ctx) expenseIDStr := c.Param("id") expenseID, err := uuid.Parse(expenseIDStr) if err != nil { logger.FromContext(ctx).WithError(err).Error("ExpenseHandler::GetExpense -> Invalid expense ID") validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid expense ID") util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "ExpenseHandler::GetExpense") return } expenseResponse := h.expenseService.GetExpenseByID(ctx, contextInfo, expenseID) if expenseResponse.HasErrors() { errorResp := expenseResponse.GetErrors()[0] logger.FromContext(ctx).WithError(errorResp).Error("ExpenseHandler::GetExpense -> Failed to get expense from service") } util.HandleResponse(c.Writer, c.Request, expenseResponse, "ExpenseHandler::GetExpense") } func (h *ExpenseHandler) ListExpenses(c *gin.Context) { ctx := c.Request.Context() contextInfo := appcontext.FromGinContext(ctx) req := &contract.ListExpenseRequest{ Page: 1, Limit: 10, } if pageStr := c.Query("page"); pageStr != "" { if page, err := strconv.Atoi(pageStr); err == nil { req.Page = page } } if limitStr := c.Query("limit"); limitStr != "" { if limit, err := strconv.Atoi(limitStr); err == nil { req.Limit = limit } } if search := c.Query("search"); search != "" { req.Search = search } if status := c.Query("status"); status != "" { req.Status = status } // Prioritize outlet_id from context (e.g. outlet-scoped user), // fall back to query param if context has no outlet. if contextInfo.OutletID != uuid.Nil { req.OutletID = contextInfo.OutletID.String() } else if outletID := c.Query("outlet_id"); outletID != "" { req.OutletID = outletID } if startDate := c.Query("start_date"); startDate != "" { req.StartDate = startDate } if endDate := c.Query("end_date"); endDate != "" { req.EndDate = endDate } validationError, validationErrorCode := h.expenseValidator.ValidateListExpenseRequest(req) if validationError != nil { validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error()) util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "ExpenseHandler::ListExpenses") return } expenseResponse := h.expenseService.ListExpenses(ctx, contextInfo, req) if expenseResponse.HasErrors() { errorResp := expenseResponse.GetErrors()[0] logger.FromContext(ctx).WithError(errorResp).Error("ExpenseHandler::ListExpenses -> Failed to list expenses from service") } util.HandleResponse(c.Writer, c.Request, expenseResponse, "ExpenseHandler::ListExpenses") }