183 lines
5.0 KiB
Go
183 lines
5.0 KiB
Go
package handler
|
|
|
|
import (
|
|
"errors"
|
|
"strconv"
|
|
|
|
"accounting-app/pkg/api"
|
|
"accounting-app/internal/service"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// CreditAccountHandler handles HTTP requests for credit account operations
|
|
type CreditAccountHandler struct {
|
|
billingService *service.BillingService
|
|
repaymentService *service.RepaymentService
|
|
}
|
|
|
|
// NewCreditAccountHandler creates a new CreditAccountHandler instance
|
|
func NewCreditAccountHandler(
|
|
billingService *service.BillingService,
|
|
repaymentService *service.RepaymentService,
|
|
) *CreditAccountHandler {
|
|
return &CreditAccountHandler{
|
|
billingService: billingService,
|
|
repaymentService: repaymentService,
|
|
}
|
|
}
|
|
|
|
// GetBills handles GET /api/v1/accounts/:id/bills
|
|
// Returns all bills for a specific credit account
|
|
func (h *CreditAccountHandler) GetBills(c *gin.Context) {
|
|
userId, exists := c.Get("user_id")
|
|
if !exists {
|
|
api.Unauthorized(c, "User not authenticated")
|
|
return
|
|
}
|
|
|
|
accountID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid account ID")
|
|
return
|
|
}
|
|
|
|
bills, err := h.billingService.GetBillsByAccountID(userId.(uint), uint(accountID))
|
|
if err != nil {
|
|
api.InternalError(c, "Failed to get bills: "+err.Error())
|
|
return
|
|
}
|
|
|
|
api.Success(c, bills)
|
|
}
|
|
|
|
// RepayInput represents the input for repayment
|
|
type RepayInput struct {
|
|
BillID uint `json:"bill_id" binding:"required"`
|
|
Amount float64 `json:"amount" binding:"required,gt=0"`
|
|
FromAccountID uint `json:"from_account_id" binding:"required"`
|
|
}
|
|
|
|
// Repay handles POST /api/v1/accounts/:id/repay
|
|
// Processes a repayment for a credit account bill
|
|
func (h *CreditAccountHandler) Repay(c *gin.Context) {
|
|
userId, exists := c.Get("user_id")
|
|
if !exists {
|
|
api.Unauthorized(c, "User not authenticated")
|
|
return
|
|
}
|
|
|
|
accountID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid account ID")
|
|
return
|
|
}
|
|
|
|
var input RepayInput
|
|
if err := c.ShouldBindJSON(&input); err != nil {
|
|
api.ValidationError(c, "Invalid request body: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Get the bill to verify it belongs to this account
|
|
bill, err := h.billingService.GetBillByID(userId.(uint), input.BillID)
|
|
if err != nil {
|
|
if errors.Is(err, service.ErrBillNotFound) {
|
|
api.NotFound(c, "Bill not found")
|
|
return
|
|
}
|
|
api.InternalError(c, "Failed to get bill: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Verify bill belongs to the specified account
|
|
if bill.AccountID != uint(accountID) {
|
|
api.BadRequest(c, "Bill does not belong to the specified account")
|
|
return
|
|
}
|
|
|
|
// Check if bill is already paid
|
|
if bill.Status == "paid" {
|
|
api.BadRequest(c, "Bill is already paid")
|
|
return
|
|
}
|
|
|
|
// Check if payment amount exceeds bill balance
|
|
if input.Amount > bill.CurrentBalance {
|
|
api.BadRequest(c, "Payment amount exceeds bill balance")
|
|
return
|
|
}
|
|
|
|
// Check if bill has a repayment plan
|
|
plan, err := h.repaymentService.GetRepaymentPlanByBillID(userId.(uint), input.BillID)
|
|
if err != nil && !errors.Is(err, service.ErrRepaymentPlanNotFound) {
|
|
api.InternalError(c, "Failed to check repayment plan: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// If bill has a repayment plan, use installment payment
|
|
if plan != nil {
|
|
// Find the next unpaid installment
|
|
var nextInstallment *uint
|
|
for _, installment := range plan.Installments {
|
|
if installment.Status != "paid" {
|
|
nextInstallment = &installment.ID
|
|
break
|
|
}
|
|
}
|
|
|
|
if nextInstallment == nil {
|
|
api.BadRequest(c, "All installments are already paid")
|
|
return
|
|
}
|
|
|
|
// Pay the installment
|
|
payInput := service.PayInstallmentInput{
|
|
InstallmentID: *nextInstallment,
|
|
Amount: input.Amount,
|
|
FromAccountID: input.FromAccountID,
|
|
}
|
|
|
|
if err := h.repaymentService.PayInstallment(userId.(uint), payInput); err != nil {
|
|
if errors.Is(err, service.ErrInvalidRepaymentAmount) {
|
|
api.BadRequest(c, "Invalid payment amount")
|
|
return
|
|
}
|
|
if errors.Is(err, service.ErrPaymentExceedsInstallment) {
|
|
api.BadRequest(c, "Payment amount exceeds installment amount")
|
|
return
|
|
}
|
|
if errors.Is(err, service.ErrInstallmentAlreadyPaid) {
|
|
api.BadRequest(c, "Installment is already paid")
|
|
return
|
|
}
|
|
api.InternalError(c, "Failed to process payment: "+err.Error())
|
|
return
|
|
}
|
|
|
|
api.Success(c, gin.H{
|
|
"message": "Payment processed successfully via repayment plan",
|
|
"plan_id": plan.ID,
|
|
})
|
|
return
|
|
}
|
|
|
|
// If no repayment plan, process as direct payment
|
|
// This would require a direct payment method in the billing service
|
|
// For now, we'll suggest creating a repayment plan for structured payments
|
|
api.Success(c, gin.H{
|
|
"message": "Direct payment not yet implemented. Please create a repayment plan first.",
|
|
"suggestion": "Create a repayment plan with POST /api/v1/repayment-plans",
|
|
})
|
|
}
|
|
|
|
// RegisterRoutes registers credit account routes to the given router group
|
|
func (h *CreditAccountHandler) RegisterRoutes(rg *gin.RouterGroup) {
|
|
// Credit account specific routes are nested under accounts
|
|
accounts := rg.Group("/accounts")
|
|
{
|
|
accounts.GET("/:id/bills", h.GetBills)
|
|
accounts.POST("/:id/repay", h.Repay)
|
|
}
|
|
}
|