package handler import ( "strconv" "accounting-app/pkg/api" "accounting-app/internal/models" "accounting-app/internal/service" "github.com/gin-gonic/gin" ) // AllocationRuleHandler handles HTTP requests for allocation rule operations type AllocationRuleHandler struct { service *service.AllocationRuleService } // NewAllocationRuleHandler creates a new AllocationRuleHandler instance func NewAllocationRuleHandler(service *service.AllocationRuleService) *AllocationRuleHandler { return &AllocationRuleHandler{ service: service, } } // RegisterRoutes registers allocation rule-related routes func (h *AllocationRuleHandler) RegisterRoutes(rg *gin.RouterGroup) { allocationRules := rg.Group("/allocation-rules") { allocationRules.POST("", h.CreateAllocationRule) allocationRules.GET("", h.GetAllAllocationRules) allocationRules.GET("/suggest", h.SuggestAllocationForIncome) allocationRules.GET("/:id", h.GetAllocationRule) allocationRules.PUT("/:id", h.UpdateAllocationRule) allocationRules.DELETE("/:id", h.DeleteAllocationRule) allocationRules.POST("/:id/apply", h.ApplyAllocationRule) } } // CreateAllocationRule handles POST /api/v1/allocation-rules func (h *AllocationRuleHandler) CreateAllocationRule(c *gin.Context) { var input service.AllocationRuleInput if err := c.ShouldBindJSON(&input); err != nil { api.ValidationError(c, err.Error()) return } // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } input.UserID = userID.(uint) rule, err := h.service.CreateAllocationRule(input) if err != nil { switch err { case service.ErrInvalidTriggerType: api.BadRequest(c, err.Error()) case service.ErrInvalidTargetType: api.BadRequest(c, err.Error()) case service.ErrInvalidAllocationPercentage: api.BadRequest(c, err.Error()) case service.ErrInvalidAllocationAmount: api.BadRequest(c, err.Error()) case service.ErrInvalidAllocationTarget: api.BadRequest(c, err.Error()) case service.ErrTotalPercentageExceeds100: api.BadRequest(c, err.Error()) case service.ErrTargetNotFound: api.BadRequest(c, err.Error()) default: api.InternalError(c, "Failed to create allocation rule") } return } api.Created(c, rule) } // GetAllocationRule handles GET /api/v1/allocation-rules/:id func (h *AllocationRuleHandler) GetAllocationRule(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid allocation rule ID") return } // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } rule, err := h.service.GetAllocationRule(userID.(uint), uint(id)) if err != nil { if err == service.ErrAllocationRuleNotFound { api.NotFound(c, "Allocation rule not found") return } api.InternalError(c, "Failed to get allocation rule") return } api.Success(c, rule) } // GetAllAllocationRules handles GET /api/v1/allocation-rules func (h *AllocationRuleHandler) GetAllAllocationRules(c *gin.Context) { // Check if we should filter by active status activeOnly := c.Query("active") == "true" var rules []models.AllocationRule var err error // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } if activeOnly { rules, err = h.service.GetActiveAllocationRules(userID.(uint)) } else { rules, err = h.service.GetAllAllocationRules(userID.(uint)) } if err != nil { api.InternalError(c, "Failed to get allocation rules") return } api.Success(c, rules) } // UpdateAllocationRule handles PUT /api/v1/allocation-rules/:id func (h *AllocationRuleHandler) UpdateAllocationRule(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid allocation rule ID") return } var input service.AllocationRuleInput if err := c.ShouldBindJSON(&input); err != nil { api.ValidationError(c, err.Error()) return } // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } input.UserID = userID.(uint) rule, err := h.service.UpdateAllocationRule(userID.(uint), uint(id), input) if err != nil { switch err { case service.ErrAllocationRuleNotFound: api.NotFound(c, "Allocation rule not found") case service.ErrInvalidTriggerType: api.BadRequest(c, err.Error()) case service.ErrInvalidTargetType: api.BadRequest(c, err.Error()) case service.ErrInvalidAllocationPercentage: api.BadRequest(c, err.Error()) case service.ErrInvalidAllocationAmount: api.BadRequest(c, err.Error()) case service.ErrInvalidAllocationTarget: api.BadRequest(c, err.Error()) case service.ErrTotalPercentageExceeds100: api.BadRequest(c, err.Error()) case service.ErrTargetNotFound: api.BadRequest(c, err.Error()) default: api.InternalError(c, "Failed to update allocation rule") } return } api.Success(c, rule) } // DeleteAllocationRule handles DELETE /api/v1/allocation-rules/:id func (h *AllocationRuleHandler) DeleteAllocationRule(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid allocation rule ID") return } // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } err = h.service.DeleteAllocationRule(userID.(uint), uint(id)) if err != nil { switch err { case service.ErrAllocationRuleNotFound: api.NotFound(c, "Allocation rule not found") case service.ErrAllocationRuleInUse: api.Conflict(c, err.Error()) default: api.InternalError(c, "Failed to delete allocation rule") } return } api.NoContent(c) } // ApplyAllocationRule handles POST /api/v1/allocation-rules/:id/apply func (h *AllocationRuleHandler) ApplyAllocationRule(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid allocation rule ID") return } var input service.ApplyAllocationInput if err := c.ShouldBindJSON(&input); err != nil { api.ValidationError(c, err.Error()) return } // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } result, err := h.service.ApplyAllocationRule(userID.(uint), uint(id), input) if err != nil { switch err { case service.ErrAllocationRuleNotFound: api.NotFound(c, "Allocation rule not found") case service.ErrInsufficientAmount: api.BadRequest(c, err.Error()) case service.ErrAccountNotFound: api.BadRequest(c, err.Error()) case service.ErrInsufficientBalance: api.BadRequest(c, err.Error()) case service.ErrTargetNotFound: api.BadRequest(c, err.Error()) case service.ErrInvalidTargetType: api.BadRequest(c, err.Error()) case service.ErrInvalidAllocationTarget: api.BadRequest(c, err.Error()) default: api.InternalError(c, "Failed to apply allocation rule") } return } api.Success(c, result) } // SuggestAllocationForIncome handles GET /api/v1/allocation-rules/suggest func (h *AllocationRuleHandler) SuggestAllocationForIncome(c *gin.Context) { // Get amount from query parameter amountStr := c.Query("amount") if amountStr == "" { api.BadRequest(c, "Amount parameter is required") return } amount, err := strconv.ParseFloat(amountStr, 64) if err != nil || amount <= 0 { api.BadRequest(c, "Invalid amount") return } // Get account_id from query parameter accountIDStr := c.Query("account_id") if accountIDStr == "" { api.BadRequest(c, "account_id parameter is required") return } accountID, err := strconv.ParseUint(accountIDStr, 10, 32) if err != nil { api.BadRequest(c, "Invalid account_id") return } // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } rules, err := h.service.SuggestAllocationForIncome(userID.(uint), amount, uint(accountID)) if err != nil { api.InternalError(c, "Failed to get allocation suggestions") return } api.Success(c, rules) }