package handler import ( "errors" "strconv" "accounting-app/pkg/api" "accounting-app/internal/models" "accounting-app/internal/service" "github.com/gin-gonic/gin" ) // ReimbursementServiceInterface defines the interface for reimbursement operations type ReimbursementServiceInterface interface { ApplyReimbursement(userID uint, transactionID uint, amount float64) (*models.Transaction, error) ConfirmReimbursement(userID uint, transactionID uint) (*models.Transaction, error) CancelReimbursement(userID uint, transactionID uint) (*models.Transaction, error) } // ReimbursementHandler handles HTTP requests for reimbursement operations // Feature: accounting-feature-upgrade // Validates: Requirements 8.1-8.9 type ReimbursementHandler struct { reimbursementService ReimbursementServiceInterface } // NewReimbursementHandler creates a new ReimbursementHandler instance func NewReimbursementHandler(reimbursementService ReimbursementServiceInterface) *ReimbursementHandler { return &ReimbursementHandler{ reimbursementService: reimbursementService, } } // ApplyReimbursementInput represents the input for applying for reimbursement type ApplyReimbursementInput struct { Amount float64 `json:"amount" binding:"required,gt=0"` } // ApplyReimbursement handles PUT /api/v1/transactions/:id/reimbursement // Applies for reimbursement on an expense transaction // Feature: accounting-feature-upgrade // Validates: Requirements 8.2, 8.3, 8.4 func (h *ReimbursementHandler) ApplyReimbursement(c *gin.Context) { userId, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid transaction ID") return } var input ApplyReimbursementInput if err := c.ShouldBindJSON(&input); err != nil { api.ValidationError(c, "Invalid request body: "+err.Error()) return } transaction, err := h.reimbursementService.ApplyReimbursement(userId.(uint), uint(id), input.Amount) if err != nil { if errors.Is(err, service.ErrTransactionNotFound) { api.NotFound(c, "Transaction not found") return } if errors.Is(err, service.ErrNotExpenseTransaction) { api.BadRequest(c, "Only expense transactions can be reimbursed") return } if errors.Is(err, service.ErrAlreadyReimbursed) { api.BadRequest(c, "Transaction is already reimbursed") return } if errors.Is(err, service.ErrInvalidReimbursementAmount) { api.BadRequest(c, "Reimbursement amount must be greater than 0 and not exceed original amount") return } api.InternalError(c, "Failed to apply reimbursement: "+err.Error()) return } api.Success(c, transaction) } // ConfirmReimbursement handles PUT /api/v1/transactions/:id/reimbursement/confirm // Confirms a pending reimbursement and creates the income record // Feature: accounting-feature-upgrade // Validates: Requirements 8.5, 8.6, 8.28 func (h *ReimbursementHandler) ConfirmReimbursement(c *gin.Context) { userId, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid transaction ID") return } reimbursementIncome, err := h.reimbursementService.ConfirmReimbursement(userId.(uint), uint(id)) if err != nil { if errors.Is(err, service.ErrTransactionNotFound) { api.NotFound(c, "Transaction not found") return } if errors.Is(err, service.ErrNotPendingReimbursement) { api.BadRequest(c, "Transaction is not in pending reimbursement status") return } if errors.Is(err, service.ErrReimbursementCategoryNotFound) { api.InternalError(c, "Reimbursement system category not found. Please run database migrations.") return } api.InternalError(c, "Failed to confirm reimbursement: "+err.Error()) return } api.Success(c, reimbursementIncome) } // CancelReimbursement handles PUT /api/v1/transactions/:id/reimbursement/cancel // Cancels a pending reimbursement // Feature: accounting-feature-upgrade // Validates: Requirements 8.9 func (h *ReimbursementHandler) CancelReimbursement(c *gin.Context) { userId, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid transaction ID") return } transaction, err := h.reimbursementService.CancelReimbursement(userId.(uint), uint(id)) if err != nil { if errors.Is(err, service.ErrTransactionNotFound) { api.NotFound(c, "Transaction not found") return } if errors.Is(err, service.ErrNotPendingReimbursement) { api.BadRequest(c, "Transaction is not in pending reimbursement status") return } api.InternalError(c, "Failed to cancel reimbursement: "+err.Error()) return } api.Success(c, transaction) } // RegisterRoutes registers all reimbursement routes to the given router group func (h *ReimbursementHandler) RegisterRoutes(rg *gin.RouterGroup) { transactions := rg.Group("/transactions") { transactions.PUT("/:id/reimbursement", h.ApplyReimbursement) transactions.PUT("/:id/reimbursement/confirm", h.ConfirmReimbursement) transactions.PUT("/:id/reimbursement/cancel", h.CancelReimbursement) } }