- Categories: regular, deposits, credits, recurring, multi-user, accounts - Transactions: deposits and withdrawals with user tracking - Recurring plans: monthly payment obligations per user - Stats: overdues calculation with allocation algorithm - Excludes is_account categories from total sums - Documentation: docs/SAVINGS.md
229 lines
9.2 KiB
Go
229 lines
9.2 KiB
Go
package model
|
|
|
|
import (
|
|
"database/sql"
|
|
"time"
|
|
)
|
|
|
|
type SavingsCategory struct {
|
|
ID int64 `db:"id" json:"id"`
|
|
UserID int64 `db:"user_id" json:"user_id"`
|
|
Name string `db:"name" json:"name"`
|
|
Description string `db:"description" json:"description"`
|
|
|
|
// Type flags
|
|
IsDeposit bool `db:"is_deposit" json:"is_deposit"`
|
|
IsCredit bool `db:"is_credit" json:"is_credit"`
|
|
IsAccount bool `db:"is_account" json:"is_account"`
|
|
IsRecurring bool `db:"is_recurring" json:"is_recurring"`
|
|
IsMulti bool `db:"is_multi" json:"is_multi"`
|
|
IsClosed bool `db:"is_closed" json:"is_closed"`
|
|
|
|
// Initial capital
|
|
InitialCapital float64 `db:"initial_capital" json:"initial_capital"`
|
|
|
|
// Deposit fields
|
|
DepositAmount float64 `db:"deposit_amount" json:"deposit_amount"`
|
|
InterestRate float64 `db:"interest_rate" json:"interest_rate"`
|
|
DepositStartDate sql.NullTime `db:"deposit_start_date" json:"-"`
|
|
DepositStartStr *string `db:"-" json:"deposit_start_date"`
|
|
DepositTerm int `db:"deposit_term" json:"deposit_term"`
|
|
DepositEndDate sql.NullTime `db:"deposit_end_date" json:"-"`
|
|
DepositEndStr *string `db:"-" json:"deposit_end_date"`
|
|
LastInterestCalc sql.NullTime `db:"last_interest_calc" json:"-"`
|
|
FinalAmount float64 `db:"final_amount" json:"final_amount"`
|
|
|
|
// Credit fields
|
|
CreditAmount float64 `db:"credit_amount" json:"credit_amount"`
|
|
CreditTerm int `db:"credit_term" json:"credit_term"`
|
|
CreditRate float64 `db:"credit_rate" json:"credit_rate"`
|
|
CreditStartDate sql.NullTime `db:"credit_start_date" json:"-"`
|
|
CreditStartStr *string `db:"-" json:"credit_start_date"`
|
|
|
|
// Recurring fields
|
|
RecurringAmount float64 `db:"recurring_amount" json:"recurring_amount"`
|
|
RecurringDay int `db:"recurring_day" json:"recurring_day"`
|
|
RecurringStartDate sql.NullTime `db:"recurring_start_date" json:"-"`
|
|
LastRecurringRun sql.NullTime `db:"last_recurring_run" json:"-"`
|
|
|
|
// Computed (populated in service)
|
|
CurrentAmount float64 `db:"-" json:"current_amount"`
|
|
RecurringTotalAmount float64 `db:"-" json:"recurring_total_amount"`
|
|
|
|
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
|
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
|
|
|
// Relations
|
|
Members []SavingsCategoryMember `db:"-" json:"members,omitempty"`
|
|
}
|
|
|
|
func (c *SavingsCategory) ProcessForJSON() {
|
|
if c.DepositStartDate.Valid {
|
|
formatted := c.DepositStartDate.Time.Format("2006-01-02")
|
|
c.DepositStartStr = &formatted
|
|
}
|
|
if c.DepositEndDate.Valid {
|
|
formatted := c.DepositEndDate.Time.Format("2006-01-02")
|
|
c.DepositEndStr = &formatted
|
|
}
|
|
if c.CreditStartDate.Valid {
|
|
formatted := c.CreditStartDate.Time.Format("2006-01-02")
|
|
c.CreditStartStr = &formatted
|
|
}
|
|
}
|
|
|
|
type SavingsTransaction struct {
|
|
ID int64 `db:"id" json:"id"`
|
|
CategoryID int64 `db:"category_id" json:"category_id"`
|
|
UserID int64 `db:"user_id" json:"user_id"`
|
|
Amount float64 `db:"amount" json:"amount"`
|
|
Type string `db:"type" json:"type"` // deposit, withdrawal
|
|
Description string `db:"description" json:"description"`
|
|
Date time.Time `db:"date" json:"date"`
|
|
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
|
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
|
CategoryName string `db:"category_name" json:"category_name,omitempty"`
|
|
UserName string `db:"user_name" json:"user_name,omitempty"`
|
|
}
|
|
|
|
type SavingsRecurringPlan struct {
|
|
ID int64 `db:"id" json:"id"`
|
|
CategoryID int64 `db:"category_id" json:"category_id"`
|
|
UserID sql.NullInt64 `db:"user_id" json:"-"`
|
|
UserIDPtr *int64 `db:"-" json:"user_id"`
|
|
Effective time.Time `db:"effective" json:"effective"`
|
|
Amount float64 `db:"amount" json:"amount"`
|
|
Day int `db:"day" json:"day"`
|
|
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
|
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
|
}
|
|
|
|
func (p *SavingsRecurringPlan) ProcessForJSON() {
|
|
if p.UserID.Valid {
|
|
p.UserIDPtr = &p.UserID.Int64
|
|
}
|
|
}
|
|
|
|
type SavingsCategoryMember struct {
|
|
ID int64 `db:"id" json:"id"`
|
|
CategoryID int64 `db:"category_id" json:"category_id"`
|
|
UserID int64 `db:"user_id" json:"user_id"`
|
|
UserName string `db:"user_name" json:"user_name,omitempty"`
|
|
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
|
}
|
|
|
|
// Request DTOs
|
|
type CreateSavingsCategoryRequest struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description,omitempty"`
|
|
IsDeposit bool `json:"is_deposit,omitempty"`
|
|
IsCredit bool `json:"is_credit,omitempty"`
|
|
IsAccount bool `json:"is_account,omitempty"`
|
|
IsRecurring bool `json:"is_recurring,omitempty"`
|
|
IsMulti bool `json:"is_multi,omitempty"`
|
|
InitialCapital float64 `json:"initial_capital,omitempty"`
|
|
DepositAmount float64 `json:"deposit_amount,omitempty"`
|
|
InterestRate float64 `json:"interest_rate,omitempty"`
|
|
DepositStartDate *string `json:"deposit_start_date,omitempty"`
|
|
DepositTerm int `json:"deposit_term,omitempty"`
|
|
CreditAmount float64 `json:"credit_amount,omitempty"`
|
|
CreditTerm int `json:"credit_term,omitempty"`
|
|
CreditRate float64 `json:"credit_rate,omitempty"`
|
|
CreditStartDate *string `json:"credit_start_date,omitempty"`
|
|
RecurringAmount float64 `json:"recurring_amount,omitempty"`
|
|
RecurringDay int `json:"recurring_day,omitempty"`
|
|
RecurringStartDate *string `json:"recurring_start_date,omitempty"`
|
|
MemberIDs []int64 `json:"member_ids,omitempty"`
|
|
}
|
|
|
|
type UpdateSavingsCategoryRequest struct {
|
|
Name *string `json:"name,omitempty"`
|
|
Description *string `json:"description,omitempty"`
|
|
IsDeposit *bool `json:"is_deposit,omitempty"`
|
|
IsCredit *bool `json:"is_credit,omitempty"`
|
|
IsAccount *bool `json:"is_account,omitempty"`
|
|
IsRecurring *bool `json:"is_recurring,omitempty"`
|
|
IsMulti *bool `json:"is_multi,omitempty"`
|
|
IsClosed *bool `json:"is_closed,omitempty"`
|
|
InitialCapital *float64 `json:"initial_capital,omitempty"`
|
|
DepositAmount *float64 `json:"deposit_amount,omitempty"`
|
|
InterestRate *float64 `json:"interest_rate,omitempty"`
|
|
DepositStartDate *string `json:"deposit_start_date,omitempty"`
|
|
DepositTerm *int `json:"deposit_term,omitempty"`
|
|
FinalAmount *float64 `json:"final_amount,omitempty"`
|
|
CreditAmount *float64 `json:"credit_amount,omitempty"`
|
|
CreditTerm *int `json:"credit_term,omitempty"`
|
|
CreditRate *float64 `json:"credit_rate,omitempty"`
|
|
CreditStartDate *string `json:"credit_start_date,omitempty"`
|
|
RecurringAmount *float64 `json:"recurring_amount,omitempty"`
|
|
RecurringDay *int `json:"recurring_day,omitempty"`
|
|
RecurringStartDate *string `json:"recurring_start_date,omitempty"`
|
|
}
|
|
|
|
type CreateSavingsTransactionRequest struct {
|
|
CategoryID int64 `json:"category_id"`
|
|
Amount float64 `json:"amount"`
|
|
Type string `json:"type"` // deposit, withdrawal
|
|
Description string `json:"description,omitempty"`
|
|
Date string `json:"date"`
|
|
}
|
|
|
|
type UpdateSavingsTransactionRequest struct {
|
|
Amount *float64 `json:"amount,omitempty"`
|
|
Type *string `json:"type,omitempty"`
|
|
Description *string `json:"description,omitempty"`
|
|
Date *string `json:"date,omitempty"`
|
|
}
|
|
|
|
type CreateRecurringPlanRequest struct {
|
|
Effective string `json:"effective"`
|
|
Amount float64 `json:"amount"`
|
|
Day int `json:"day,omitempty"`
|
|
UserID *int64 `json:"user_id,omitempty"`
|
|
}
|
|
|
|
type UpdateRecurringPlanRequest struct {
|
|
Effective *string `json:"effective,omitempty"`
|
|
Amount *float64 `json:"amount,omitempty"`
|
|
Day *int `json:"day,omitempty"`
|
|
}
|
|
|
|
type SavingsStats struct {
|
|
MonthlyPayments float64 `json:"monthly_payments"`
|
|
MonthlyPaymentDetails []MonthlyPaymentDetail `json:"monthly_payment_details"`
|
|
Overdues []OverduePayment `json:"overdues"`
|
|
TotalBalance float64 `json:"total_balance"`
|
|
TotalDeposits float64 `json:"total_deposits"`
|
|
TotalWithdrawals float64 `json:"total_withdrawals"`
|
|
CategoriesCount int `json:"categories_count"`
|
|
ByCategory []CategoryStats `json:"by_category"`
|
|
}
|
|
|
|
type CategoryStats struct {
|
|
CategoryID int64 `json:"category_id"`
|
|
CategoryName string `json:"category_name"`
|
|
Balance float64 `json:"balance"`
|
|
IsDeposit bool `json:"is_deposit"`
|
|
IsRecurring bool `json:"is_recurring"`
|
|
}
|
|
|
|
// MonthlyPaymentDetail represents a recurring payment detail
|
|
type MonthlyPaymentDetail struct {
|
|
CategoryID int64 `json:"category_id"`
|
|
CategoryName string `json:"category_name"`
|
|
Amount float64 `json:"amount"`
|
|
Day int `json:"day"`
|
|
}
|
|
|
|
// OverduePayment represents an overdue recurring payment
|
|
type OverduePayment struct {
|
|
CategoryID int64 `json:"category_id"`
|
|
CategoryName string `json:"category_name"`
|
|
UserID int64 `json:"user_id"`
|
|
UserName string `json:"user_name"`
|
|
Amount float64 `json:"amount"`
|
|
DueDay int `json:"due_day"`
|
|
DaysOverdue int `json:"days_overdue"`
|
|
Month string `json:"month"`
|
|
}
|