This commit is contained in:
2026-01-25 21:59:00 +08:00
parent 7fd537bef3
commit 4cad3f0250
118 changed files with 30473 additions and 0 deletions

View File

@@ -0,0 +1,314 @@
package repository
import (
"errors"
"fmt"
"time"
"accounting-app/internal/models"
"gorm.io/gorm"
)
// Repayment repository errors
var (
ErrRepaymentPlanNotFound = errors.New("repayment plan not found")
ErrInstallmentNotFound = errors.New("installment not found")
ErrReminderNotFound = errors.New("reminder not found")
)
// RepaymentRepository handles database operations for repayment plans and installments
type RepaymentRepository struct {
db *gorm.DB
}
// NewRepaymentRepository creates a new RepaymentRepository instance
func NewRepaymentRepository(db *gorm.DB) *RepaymentRepository {
return &RepaymentRepository{db: db}
}
// ========================================
// Repayment Plan Operations
// ========================================
// CreatePlan creates a new repayment plan
func (r *RepaymentRepository) CreatePlan(plan *models.RepaymentPlan) error {
if err := r.db.Create(plan).Error; err != nil {
return fmt.Errorf("failed to create repayment plan: %w", err)
}
return nil
}
// GetPlanByID retrieves a repayment plan by its ID
func (r *RepaymentRepository) GetPlanByID(userID uint, id uint) (*models.RepaymentPlan, error) {
var plan models.RepaymentPlan
if err := r.db.Where("user_id = ?", userID).Preload("Bill").Preload("Bill.Account").Preload("Installments").First(&plan, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrRepaymentPlanNotFound
}
return nil, fmt.Errorf("failed to get repayment plan: %w", err)
}
return &plan, nil
}
// GetPlanByBillID retrieves a repayment plan by bill ID
func (r *RepaymentRepository) GetPlanByBillID(userID uint, billID uint) (*models.RepaymentPlan, error) {
var plan models.RepaymentPlan
if err := r.db.Where("user_id = ? AND bill_id = ?", userID, billID).
Preload("Bill").
Preload("Bill.Account").
Preload("Installments").
First(&plan).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrRepaymentPlanNotFound
}
return nil, fmt.Errorf("failed to get repayment plan by bill: %w", err)
}
return &plan, nil
}
// GetActivePlans retrieves all active repayment plans
func (r *RepaymentRepository) GetActivePlans(userID uint) ([]models.RepaymentPlan, error) {
var plans []models.RepaymentPlan
if err := r.db.Where("user_id = ? AND status = ?", userID, models.RepaymentPlanStatusActive).
Preload("Bill").
Preload("Bill.Account").
Preload("Installments").
Find(&plans).Error; err != nil {
return nil, fmt.Errorf("failed to get active plans: %w", err)
}
return plans, nil
}
// UpdatePlan updates an existing repayment plan
func (r *RepaymentRepository) UpdatePlan(plan *models.RepaymentPlan) error {
var existing models.RepaymentPlan
if err := r.db.First(&existing, plan.ID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrRepaymentPlanNotFound
}
return fmt.Errorf("failed to check plan existence: %w", err)
}
if err := r.db.Save(plan).Error; err != nil {
return fmt.Errorf("failed to update repayment plan: %w", err)
}
return nil
}
// UpdatePlanStatus updates the status of a repayment plan
func (r *RepaymentRepository) UpdatePlanStatus(id uint, status models.RepaymentPlanStatus) error {
result := r.db.Model(&models.RepaymentPlan{}).Where("id = ?", id).Update("status", status)
if result.Error != nil {
return fmt.Errorf("failed to update plan status: %w", result.Error)
}
if result.RowsAffected == 0 {
return ErrRepaymentPlanNotFound
}
return nil
}
// DeletePlan deletes a repayment plan and its installments
func (r *RepaymentRepository) DeletePlan(id uint) error {
return r.db.Transaction(func(tx *gorm.DB) error {
// Delete installments first
if err := tx.Where("plan_id = ?", id).Delete(&models.RepaymentInstallment{}).Error; err != nil {
return fmt.Errorf("failed to delete installments: %w", err)
}
// Delete the plan
result := tx.Delete(&models.RepaymentPlan{}, id)
if result.Error != nil {
return fmt.Errorf("failed to delete plan: %w", result.Error)
}
if result.RowsAffected == 0 {
return ErrRepaymentPlanNotFound
}
return nil
})
}
// ========================================
// Installment Operations
// ========================================
// CreateInstallment creates a new installment
func (r *RepaymentRepository) CreateInstallment(installment *models.RepaymentInstallment) error {
if err := r.db.Create(installment).Error; err != nil {
return fmt.Errorf("failed to create installment: %w", err)
}
return nil
}
// GetInstallmentByID retrieves an installment by its ID
func (r *RepaymentRepository) GetInstallmentByID(id uint) (*models.RepaymentInstallment, error) {
var installment models.RepaymentInstallment
if err := r.db.Preload("Plan").Preload("Plan.Bill").First(&installment, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrInstallmentNotFound
}
return nil, fmt.Errorf("failed to get installment: %w", err)
}
return &installment, nil
}
// GetInstallmentsByPlanID retrieves all installments for a plan
func (r *RepaymentRepository) GetInstallmentsByPlanID(planID uint) ([]models.RepaymentInstallment, error) {
var installments []models.RepaymentInstallment
if err := r.db.Where("plan_id = ?", planID).
Order("sequence ASC").
Find(&installments).Error; err != nil {
return nil, fmt.Errorf("failed to get installments: %w", err)
}
return installments, nil
}
// GetPendingInstallments retrieves all pending installments
func (r *RepaymentRepository) GetPendingInstallments() ([]models.RepaymentInstallment, error) {
var installments []models.RepaymentInstallment
if err := r.db.Where("status = ?", models.RepaymentInstallmentStatusPending).
Preload("Plan").
Preload("Plan.Bill").
Preload("Plan.Bill.Account").
Order("due_date ASC").
Find(&installments).Error; err != nil {
return nil, fmt.Errorf("failed to get pending installments: %w", err)
}
return installments, nil
}
// GetInstallmentsDueInRange retrieves installments due within a date range
func (r *RepaymentRepository) GetInstallmentsDueInRange(startDate, endDate time.Time) ([]models.RepaymentInstallment, error) {
var installments []models.RepaymentInstallment
if err := r.db.Where("due_date >= ? AND due_date <= ? AND status = ?",
startDate, endDate, models.RepaymentInstallmentStatusPending).
Preload("Plan").
Preload("Plan.Bill").
Preload("Plan.Bill.Account").
Order("due_date ASC").
Find(&installments).Error; err != nil {
return nil, fmt.Errorf("failed to get installments due in range: %w", err)
}
return installments, nil
}
// UpdateInstallment updates an existing installment
func (r *RepaymentRepository) UpdateInstallment(installment *models.RepaymentInstallment) error {
var existing models.RepaymentInstallment
if err := r.db.First(&existing, installment.ID).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrInstallmentNotFound
}
return fmt.Errorf("failed to check installment existence: %w", err)
}
if err := r.db.Save(installment).Error; err != nil {
return fmt.Errorf("failed to update installment: %w", err)
}
return nil
}
// UpdateInstallmentStatus updates the status of an installment
func (r *RepaymentRepository) UpdateInstallmentStatus(id uint, status models.RepaymentInstallmentStatus) error {
result := r.db.Model(&models.RepaymentInstallment{}).Where("id = ?", id).Update("status", status)
if result.Error != nil {
return fmt.Errorf("failed to update installment status: %w", result.Error)
}
if result.RowsAffected == 0 {
return ErrInstallmentNotFound
}
return nil
}
// MarkInstallmentAsPaid marks an installment as paid
func (r *RepaymentRepository) MarkInstallmentAsPaid(id uint, paidAmount float64, paidAt time.Time) error {
result := r.db.Model(&models.RepaymentInstallment{}).Where("id = ?", id).Updates(map[string]interface{}{
"status": models.RepaymentInstallmentStatusPaid,
"paid_amount": paidAmount,
"paid_at": paidAt,
})
if result.Error != nil {
return fmt.Errorf("failed to mark installment as paid: %w", result.Error)
}
if result.RowsAffected == 0 {
return ErrInstallmentNotFound
}
return nil
}
// ========================================
// Payment Reminder Operations
// ========================================
// CreateReminder creates a new payment reminder
func (r *RepaymentRepository) CreateReminder(reminder *models.PaymentReminder) error {
if err := r.db.Create(reminder).Error; err != nil {
return fmt.Errorf("failed to create reminder: %w", err)
}
return nil
}
// GetReminderByID retrieves a reminder by its ID
func (r *RepaymentRepository) GetReminderByID(id uint) (*models.PaymentReminder, error) {
var reminder models.PaymentReminder
if err := r.db.Preload("Bill").Preload("Bill.Account").Preload("Installment").First(&reminder, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrReminderNotFound
}
return nil, fmt.Errorf("failed to get reminder: %w", err)
}
return &reminder, nil
}
// GetUnreadReminders retrieves all unread reminders
func (r *RepaymentRepository) GetUnreadReminders(userID uint) ([]models.PaymentReminder, error) {
var reminders []models.PaymentReminder
if err := r.db.Where("user_id = ? AND is_read = ?", userID, false).
Preload("Bill").
Preload("Bill.Account").
Preload("Installment").
Order("reminder_date ASC").
Find(&reminders).Error; err != nil {
return nil, fmt.Errorf("failed to get unread reminders: %w", err)
}
return reminders, nil
}
// GetRemindersByDateRange retrieves reminders within a date range
func (r *RepaymentRepository) GetRemindersByDateRange(startDate, endDate time.Time) ([]models.PaymentReminder, error) {
var reminders []models.PaymentReminder
if err := r.db.Where("reminder_date >= ? AND reminder_date <= ?", startDate, endDate).
Preload("Bill").
Preload("Bill.Account").
Preload("Installment").
Order("reminder_date ASC").
Find(&reminders).Error; err != nil {
return nil, fmt.Errorf("failed to get reminders by date range: %w", err)
}
return reminders, nil
}
// MarkReminderAsRead marks a reminder as read
func (r *RepaymentRepository) MarkReminderAsRead(id uint) error {
result := r.db.Model(&models.PaymentReminder{}).Where("id = ?", id).Update("is_read", true)
if result.Error != nil {
return fmt.Errorf("failed to mark reminder as read: %w", result.Error)
}
if result.RowsAffected == 0 {
return ErrReminderNotFound
}
return nil
}
// DeleteReminder deletes a reminder
func (r *RepaymentRepository) DeleteReminder(id uint) error {
result := r.db.Delete(&models.PaymentReminder{}, id)
if result.Error != nil {
return fmt.Errorf("failed to delete reminder: %w", result.Error)
}
if result.RowsAffected == 0 {
return ErrReminderNotFound
}
return nil
}