Files
2026-01-25 21:59:00 +08:00

936 lines
37 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package models
import (
"time"
"gorm.io/gorm"
)
// BaseModel contains common fields for all models
type BaseModel struct {
ID uint `gorm:"primarykey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TransactionType represents the type of transaction
type TransactionType string
const (
TransactionTypeIncome TransactionType = "income"
TransactionTypeExpense TransactionType = "expense"
TransactionTypeTransfer TransactionType = "transfer"
)
// AccountType represents the type of account
type AccountType string
const (
AccountTypeCash AccountType = "cash"
AccountTypeDebitCard AccountType = "debit_card"
AccountTypeCreditCard AccountType = "credit_card"
AccountTypeEWallet AccountType = "e_wallet"
AccountTypeCreditLine AccountType = "credit_line" // 花呗、白<E38081>?
AccountTypeInvestment AccountType = "investment"
)
// FrequencyType represents the frequency of recurring transactions
type FrequencyType string
const (
FrequencyDaily FrequencyType = "daily"
FrequencyWeekly FrequencyType = "weekly"
FrequencyMonthly FrequencyType = "monthly"
FrequencyYearly FrequencyType = "yearly"
)
// SubAccountType represents the type of sub-account
// Feature: financial-core-upgrade
// Validates: Requirements 1.2
type SubAccountType string
const (
SubAccountTypeSavingsPot SubAccountType = "savings_pot" // 存钱罐,冻结资金
SubAccountTypeMoneyFund SubAccountType = "money_fund" // 货币基金如余额宝支持利<E68C81>?
SubAccountTypeInvestment SubAccountType = "investment" // 投资账户(如股票/基金<E59FBA>?
)
// TransactionSubType represents the sub-type of transaction
// Feature: financial-core-upgrade
// Validates: Requirements 3.2
type TransactionSubType string
const (
TransactionSubTypeInterest TransactionSubType = "interest" // 利息收入
TransactionSubTypeTransferIn TransactionSubType = "transfer_in" // 转入
TransactionSubTypeTransferOut TransactionSubType = "transfer_out" // 转出
TransactionSubTypeSavingsDeposit TransactionSubType = "savings_deposit" // 存钱罐存<E7BD90>?
TransactionSubTypeSavingsWithdraw TransactionSubType = "savings_withdraw" // 存钱罐取<E7BD90>?
)
// PeriodType represents the period type for budgets
type PeriodType string
const (
PeriodTypeDaily PeriodType = "daily"
PeriodTypeWeekly PeriodType = "weekly"
PeriodTypeMonthly PeriodType = "monthly"
PeriodTypeYearly PeriodType = "yearly"
)
// PiggyBankType represents the type of piggy bank
type PiggyBankType string
const (
PiggyBankTypeManual PiggyBankType = "manual"
PiggyBankTypeAuto PiggyBankType = "auto"
PiggyBankTypeFixedDeposit PiggyBankType = "fixed_deposit"
PiggyBankTypeWeek52 PiggyBankType = "week_52"
)
// Currency represents supported currencies
type Currency string
const (
// Major currencies
CurrencyCNY Currency = "CNY"
CurrencyUSD Currency = "USD"
CurrencyEUR Currency = "EUR"
CurrencyJPY Currency = "JPY"
CurrencyGBP Currency = "GBP"
CurrencyHKD Currency = "HKD"
// Asia Pacific
CurrencyAUD Currency = "AUD"
CurrencyNZD Currency = "NZD"
CurrencySGD Currency = "SGD"
CurrencyKRW Currency = "KRW"
CurrencyTHB Currency = "THB"
CurrencyTWD Currency = "TWD"
CurrencyMOP Currency = "MOP"
CurrencyPHP Currency = "PHP"
CurrencyIDR Currency = "IDR"
CurrencyINR Currency = "INR"
CurrencyVND Currency = "VND"
CurrencyMNT Currency = "MNT"
CurrencyKHR Currency = "KHR"
CurrencyNPR Currency = "NPR"
CurrencyPKR Currency = "PKR"
CurrencyBND Currency = "BND"
// Europe
CurrencyCHF Currency = "CHF"
CurrencySEK Currency = "SEK"
CurrencyNOK Currency = "NOK"
CurrencyDKK Currency = "DKK"
CurrencyCZK Currency = "CZK"
CurrencyHUF Currency = "HUF"
CurrencyRUB Currency = "RUB"
CurrencyTRY Currency = "TRY"
// Americas
CurrencyCAD Currency = "CAD"
CurrencyMXN Currency = "MXN"
CurrencyBRL Currency = "BRL"
// Middle East & Africa
CurrencyAED Currency = "AED"
CurrencySAR Currency = "SAR"
CurrencyQAR Currency = "QAR"
CurrencyKWD Currency = "KWD"
CurrencyILS Currency = "ILS"
CurrencyZAR Currency = "ZAR"
)
// SupportedCurrencies returns a list of all supported currencies
func SupportedCurrencies() []Currency {
return []Currency{
// Major currencies
CurrencyCNY,
CurrencyUSD,
CurrencyEUR,
CurrencyJPY,
CurrencyGBP,
CurrencyHKD,
// Asia Pacific
CurrencyAUD,
CurrencyNZD,
CurrencySGD,
CurrencyKRW,
CurrencyTHB,
CurrencyTWD,
CurrencyMOP,
CurrencyPHP,
CurrencyIDR,
CurrencyINR,
CurrencyVND,
CurrencyMNT,
CurrencyKHR,
CurrencyNPR,
CurrencyPKR,
CurrencyBND,
// Europe
CurrencyCHF,
CurrencySEK,
CurrencyNOK,
CurrencyDKK,
CurrencyCZK,
CurrencyHUF,
CurrencyRUB,
CurrencyTRY,
// Americas
CurrencyCAD,
CurrencyMXN,
CurrencyBRL,
// Middle East & Africa
CurrencyAED,
CurrencySAR,
CurrencyQAR,
CurrencyKWD,
CurrencyILS,
CurrencyZAR,
}
}
// CategoryType represents whether a category is for income or expense
type CategoryType string
const (
CategoryTypeIncome CategoryType = "income"
CategoryTypeExpense CategoryType = "expense"
)
// TriggerType represents the trigger type for allocation rules
type TriggerType string
const (
TriggerTypeIncome TriggerType = "income"
TriggerTypeManual TriggerType = "manual"
)
// TargetType represents the target type for allocation
type TargetType string
const (
TargetTypeAccount TargetType = "account"
TargetTypePiggyBank TargetType = "piggy_bank"
)
// ========================================
// Database Models
// ========================================
// Account represents a financial account (cash, bank card, credit card, etc.)
type Account struct {
BaseModel
UserID uint `gorm:"not null;index" json:"user_id"`
Name string `gorm:"size:100;not null" json:"name"`
Type AccountType `gorm:"size:20;not null" json:"type"`
Balance float64 `gorm:"type:decimal(15,2);default:0" json:"balance"`
Currency Currency `gorm:"size:10;not null;default:'CNY'" json:"currency"`
Icon string `gorm:"size:50" json:"icon"`
BillingDate *int `gorm:"type:integer" json:"billing_date,omitempty"` // Day of month for credit card billing
PaymentDate *int `gorm:"type:integer" json:"payment_date,omitempty"` // Day of month for credit card payment
IsCredit bool `gorm:"default:false" json:"is_credit"`
// Asset management enhancements
// Feature: accounting-feature-upgrade
// Validates: Requirements 1.2-1.10
SortOrder int `gorm:"default:0" json:"sort_order"` // Display order for account list
WarningThreshold *float64 `gorm:"type:decimal(15,2)" json:"warning_threshold,omitempty"` // Balance warning threshold
LastSyncTime *time.Time `json:"last_sync_time,omitempty"` // Last synchronization time
AccountCode string `gorm:"size:50" json:"account_code,omitempty"` // Account identifier (e.g., Alipay, Wechat)
AccountType string `gorm:"size:20;default:'asset'" json:"account_type"` // asset or liability
// Sub-account fields
// Feature: financial-core-upgrade
// Validates: Requirements 1.1, 1.3, 2.7
ParentAccountID *uint `gorm:"index" json:"parent_account_id,omitempty"`
SubAccountType *SubAccountType `gorm:"size:20" json:"sub_account_type,omitempty"`
// Balance management for sub-accounts
// Feature: financial-core-upgrade
// Validates: Requirements 2.1-2.6
FrozenBalance float64 `gorm:"type:decimal(15,2);default:0" json:"frozen_balance"`
AvailableBalance float64 `gorm:"type:decimal(15,2);default:0" json:"available_balance"`
// Savings pot fields
// Feature: financial-core-upgrade
// Validates: Requirements 2.7
TargetAmount *float64 `gorm:"type:decimal(15,2)" json:"target_amount,omitempty"`
TargetDate *time.Time `gorm:"type:date" json:"target_date,omitempty"`
// Interest fields
// Feature: financial-core-upgrade
// Validates: Requirements 3.1
AnnualRate *float64 `gorm:"type:decimal(5,4)" json:"annual_rate,omitempty"`
InterestEnabled bool `gorm:"default:false" json:"interest_enabled"`
// Relationships
Transactions []Transaction `gorm:"foreignKey:AccountID" json:"-"`
RecurringTransactions []RecurringTransaction `gorm:"foreignKey:AccountID" json:"-"`
Budgets []Budget `gorm:"foreignKey:AccountID" json:"-"`
PiggyBanks []PiggyBank `gorm:"foreignKey:LinkedAccountID" json:"-"`
ParentAccount *Account `gorm:"foreignKey:ParentAccountID" json:"parent_account,omitempty"`
SubAccounts []Account `gorm:"foreignKey:ParentAccountID" json:"sub_accounts,omitempty"`
}
// TableName specifies the table name for Account
func (Account) TableName() string {
return "accounts"
}
// TotalBalance calculates the total balance including sub-accounts
// Feature: financial-core-upgrade
// Validates: Requirements 1.3
func (a *Account) TotalBalance() float64 {
total := a.AvailableBalance + a.FrozenBalance
for _, sub := range a.SubAccounts {
if sub.SubAccountType != nil && *sub.SubAccountType != SubAccountTypeSavingsPot {
total += sub.Balance
}
}
return total
}
// Category represents a transaction category with optional parent-child hierarchy
type Category struct {
ID uint `gorm:"primarykey" json:"id"`
UserID uint `gorm:"not null;index" json:"user_id"`
Name string `gorm:"size:50;not null" json:"name"`
Icon string `gorm:"size:50" json:"icon"`
Type CategoryType `gorm:"size:20;not null" json:"type"` // income or expense
ParentID *uint `gorm:"index" json:"parent_id,omitempty"`
SortOrder int `gorm:"default:0" json:"sort_order"`
CreatedAt time.Time `json:"created_at"`
// Relationships
Parent *Category `gorm:"foreignKey:ParentID" json:"parent,omitempty"`
Children []Category `gorm:"foreignKey:ParentID" json:"children,omitempty"`
Transactions []Transaction `gorm:"foreignKey:CategoryID" json:"-"`
Budgets []Budget `gorm:"foreignKey:CategoryID" json:"-"`
}
// TableName specifies the table name for Category
func (Category) TableName() string {
return "categories"
}
// Tag represents a label that can be attached to transactions
type Tag struct {
ID uint `gorm:"primarykey" json:"id"`
UserID uint `gorm:"not null;index" json:"user_id"`
Name string `gorm:"size:50;not null" json:"name"`
Color string `gorm:"size:20" json:"color"`
CreatedAt time.Time `json:"created_at"`
// Relationships
Transactions []Transaction `gorm:"many2many:transaction_tags;" json:"-"`
}
// TableName specifies the table name for Tag
func (Tag) TableName() string {
return "tags"
}
// Transaction represents a single financial transaction
type Transaction struct {
BaseModel
UserID uint `gorm:"not null;index" json:"user_id"`
Amount float64 `gorm:"type:decimal(15,2);not null" json:"amount"`
Type TransactionType `gorm:"size:20;not null" json:"type"`
CategoryID uint `gorm:"not null;index" json:"category_id"`
AccountID uint `gorm:"not null;index" json:"account_id"`
Currency Currency `gorm:"size:10;not null;default:'CNY'" json:"currency"`
TransactionDate time.Time `gorm:"type:date;not null;index" json:"transaction_date"`
Note string `gorm:"size:500" json:"note,omitempty"`
ImagePath string `gorm:"size:255" json:"image_path,omitempty"`
RecurringID *uint `gorm:"index" json:"recurring_id,omitempty"`
// For transfer transactions
ToAccountID *uint `gorm:"index" json:"to_account_id,omitempty"`
// Multi-ledger support
// Feature: accounting-feature-upgrade
// Validates: Requirements 3.10
LedgerID *uint `gorm:"index" json:"ledger_id,omitempty"`
// Precise time recording
// Feature: accounting-feature-upgrade
// Validates: Requirements 5.2
TransactionTime *time.Time `gorm:"type:time" json:"transaction_time,omitempty"`
// Transaction sub-type for special transactions
// Feature: financial-core-upgrade
// Validates: Requirements 3.2
SubType *TransactionSubType `gorm:"size:20" json:"sub_type,omitempty"` // interest, transfer_in, transfer_out, savings_deposit, savings_withdraw
// Reimbursement related fields
// Feature: accounting-feature-upgrade
// Validates: Requirements 8.4-8.9
ReimbursementStatus string `gorm:"size:20;default:'none'" json:"reimbursement_status"` // none, pending, completed
ReimbursementAmount *float64 `gorm:"type:decimal(15,2)" json:"reimbursement_amount,omitempty"`
ReimbursementIncomeID *uint `gorm:"index" json:"reimbursement_income_id,omitempty"`
// Refund related fields
// Feature: accounting-feature-upgrade
// Validates: Requirements 8.10-8.18
RefundStatus string `gorm:"size:20;default:'none'" json:"refund_status"` // none, partial, full
RefundAmount *float64 `gorm:"type:decimal(15,2)" json:"refund_amount,omitempty"`
RefundIncomeID *uint `gorm:"index" json:"refund_income_id,omitempty"`
// Link to original transaction (for refund/reimbursement income records)
// Feature: accounting-feature-upgrade
// Validates: Requirements 8.19-8.22
OriginalTransactionID *uint `gorm:"index" json:"original_transaction_id,omitempty"`
IncomeType string `gorm:"size:20" json:"income_type,omitempty"` // normal, refund, reimbursement
// Relationships
Category Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
Account Account `gorm:"foreignKey:AccountID" json:"account,omitempty"`
ToAccount *Account `gorm:"foreignKey:ToAccountID" json:"to_account,omitempty"`
Recurring *RecurringTransaction `gorm:"foreignKey:RecurringID" json:"recurring,omitempty"`
Tags []Tag `gorm:"many2many:transaction_tags;" json:"tags,omitempty"`
Ledger *Ledger `gorm:"foreignKey:LedgerID" json:"ledger,omitempty"`
Images []TransactionImage `gorm:"foreignKey:TransactionID" json:"images,omitempty"`
OriginalTransaction *Transaction `gorm:"foreignKey:OriginalTransactionID" json:"original_transaction,omitempty"`
}
// TableName specifies the table name for Transaction
func (Transaction) TableName() string {
return "transactions"
}
// TransactionTag represents the many-to-many relationship between transactions and tags
type TransactionTag struct {
TransactionID uint `gorm:"primaryKey" json:"transaction_id"`
TagID uint `gorm:"primaryKey" json:"tag_id"`
}
// TableName specifies the table name for TransactionTag
func (TransactionTag) TableName() string {
return "transaction_tags"
}
// Budget represents a spending budget for a category or account
type Budget struct {
BaseModel
UserID uint `gorm:"not null;index" json:"user_id"`
Name string `gorm:"size:100;not null" json:"name"`
Amount float64 `gorm:"type:decimal(15,2);not null" json:"amount"`
PeriodType PeriodType `gorm:"size:20;not null" json:"period_type"`
CategoryID *uint `gorm:"index" json:"category_id,omitempty"`
AccountID *uint `gorm:"index" json:"account_id,omitempty"`
IsRolling bool `gorm:"default:false" json:"is_rolling"`
StartDate time.Time `gorm:"type:date;not null" json:"start_date"`
EndDate *time.Time `gorm:"type:date" json:"end_date,omitempty"`
// Relationships
Category *Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
Account *Account `gorm:"foreignKey:AccountID" json:"account,omitempty"`
}
// TableName specifies the table name for Budget
func (Budget) TableName() string {
return "budgets"
}
// PiggyBank represents a savings goal
type PiggyBank struct {
BaseModel
UserID uint `gorm:"not null;index" json:"user_id"`
Name string `gorm:"size:100;not null" json:"name"`
TargetAmount float64 `gorm:"type:decimal(15,2);not null" json:"target_amount"`
CurrentAmount float64 `gorm:"type:decimal(15,2);default:0" json:"current_amount"`
Type PiggyBankType `gorm:"size:20;not null" json:"type"`
TargetDate *time.Time `gorm:"type:date" json:"target_date,omitempty"`
LinkedAccountID *uint `gorm:"index" json:"linked_account_id,omitempty"`
AutoRule string `gorm:"size:255" json:"auto_rule,omitempty"` // JSON string for auto deposit rules
// Relationships
LinkedAccount *Account `gorm:"foreignKey:LinkedAccountID" json:"linked_account,omitempty"`
}
// TableName specifies the table name for PiggyBank
func (PiggyBank) TableName() string {
return "piggy_banks"
}
// RecurringTransaction represents a template for recurring transactions
type RecurringTransaction struct {
BaseModel
UserID uint `gorm:"not null;index" json:"user_id"`
Amount float64 `gorm:"type:decimal(15,2);not null" json:"amount"`
Type TransactionType `gorm:"size:20;not null" json:"type"`
CategoryID uint `gorm:"not null;index" json:"category_id"`
AccountID uint `gorm:"not null;index" json:"account_id"`
Currency Currency `gorm:"size:10;not null;default:'CNY'" json:"currency"`
Note string `gorm:"size:500" json:"note,omitempty"`
Frequency FrequencyType `gorm:"size:20;not null" json:"frequency"`
StartDate time.Time `gorm:"type:date;not null" json:"start_date"`
EndDate *time.Time `gorm:"type:date" json:"end_date,omitempty"`
NextOccurrence time.Time `gorm:"type:date;not null" json:"next_occurrence"`
IsActive bool `gorm:"default:true" json:"is_active"`
// Relationships
Category Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
Account Account `gorm:"foreignKey:AccountID" json:"account,omitempty"`
Transactions []Transaction `gorm:"foreignKey:RecurringID" json:"-"`
}
// TableName specifies the table name for RecurringTransaction
func (RecurringTransaction) TableName() string {
return "recurring_transactions"
}
// AllocationRule represents a rule for automatically allocating income
type AllocationRule struct {
BaseModel
UserID uint `gorm:"not null;index" json:"user_id"`
Name string `gorm:"size:100;not null" json:"name"`
TriggerType TriggerType `gorm:"size:20;not null" json:"trigger_type"`
SourceAccountID *uint `gorm:"index" json:"source_account_id,omitempty"` // 触发分配的源账户,为空则匹配所有账户
IsActive bool `gorm:"default:true" json:"is_active"`
// Relationships
SourceAccount *Account `gorm:"foreignKey:SourceAccountID" json:"source_account,omitempty"`
Targets []AllocationTarget `gorm:"foreignKey:RuleID" json:"targets,omitempty"`
}
// TableName specifies the table name for AllocationRule
func (AllocationRule) TableName() string {
return "allocation_rules"
}
// AllocationTarget represents a target for income allocation
type AllocationTarget struct {
ID uint `gorm:"primarykey" json:"id"`
RuleID uint `gorm:"not null;index" json:"rule_id"`
TargetType TargetType `gorm:"size:20;not null" json:"target_type"`
TargetID uint `gorm:"not null" json:"target_id"` // Account ID or PiggyBank ID
Percentage *float64 `gorm:"type:decimal(5,2)" json:"percentage,omitempty"`
FixedAmount *float64 `gorm:"type:decimal(15,2)" json:"fixed_amount,omitempty"`
// Relationships
Rule AllocationRule `gorm:"foreignKey:RuleID" json:"-"`
}
// TableName specifies the table name for AllocationTarget
func (AllocationTarget) TableName() string {
return "allocation_targets"
}
// AllocationRecord represents a historical record of an allocation execution
// This is a duplicate definition - the correct one is below at line 627
// Keeping this comment for reference but removing the duplicate struct
// ExchangeRate represents currency exchange rates
type ExchangeRate struct {
ID uint `gorm:"primarykey" json:"id"`
FromCurrency Currency `gorm:"size:10;not null;index:idx_currency_pair" json:"from_currency"`
ToCurrency Currency `gorm:"size:10;not null;index:idx_currency_pair" json:"to_currency"`
Rate float64 `gorm:"type:decimal(15,6);not null" json:"rate"`
EffectiveDate time.Time `gorm:"type:date;not null;index" json:"effective_date"`
}
// TableName specifies the table name for ExchangeRate
func (ExchangeRate) TableName() string {
return "exchange_rates"
}
// ClassificationRule represents a rule for smart category classification
type ClassificationRule struct {
ID uint `gorm:"primarykey" json:"id"`
UserID uint `gorm:"not null;index" json:"user_id"`
Keyword string `gorm:"size:100;not null;index" json:"keyword"`
CategoryID uint `gorm:"not null;index" json:"category_id"`
MinAmount *float64 `gorm:"type:decimal(15,2)" json:"min_amount,omitempty"`
MaxAmount *float64 `gorm:"type:decimal(15,2)" json:"max_amount,omitempty"`
HitCount int `gorm:"default:0" json:"hit_count"`
// Relationships
Category Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
}
// TableName specifies the table name for ClassificationRule
func (ClassificationRule) TableName() string {
return "classification_rules"
}
// BillStatus represents the status of a credit card bill
type BillStatus string
const (
BillStatusPending BillStatus = "pending" // Bill generated, not yet paid
BillStatusPaid BillStatus = "paid" // Bill fully paid
BillStatusOverdue BillStatus = "overdue" // Payment date passed, not paid
)
// CreditCardBill represents a credit card billing cycle statement
type CreditCardBill struct {
BaseModel
UserID uint `gorm:"not null;index" json:"user_id"`
AccountID uint `gorm:"not null;index" json:"account_id"`
BillingDate time.Time `gorm:"type:date;not null;index" json:"billing_date"` // Statement date
PaymentDueDate time.Time `gorm:"type:date;not null;index" json:"payment_due_date"` // Payment due date
PreviousBalance float64 `gorm:"type:decimal(15,2);default:0" json:"previous_balance"` // Balance from previous bill
TotalSpending float64 `gorm:"type:decimal(15,2);default:0" json:"total_spending"` // Total spending in this cycle
TotalPayment float64 `gorm:"type:decimal(15,2);default:0" json:"total_payment"` // Total payments made in this cycle
CurrentBalance float64 `gorm:"type:decimal(15,2);default:0" json:"current_balance"` // Outstanding balance
MinimumPayment float64 `gorm:"type:decimal(15,2);default:0" json:"minimum_payment"` // Minimum payment required
Status BillStatus `gorm:"size:20;not null;default:'pending'" json:"status"`
PaidAmount float64 `gorm:"type:decimal(15,2);default:0" json:"paid_amount"` // Amount paid towards this bill
PaidAt *time.Time `gorm:"type:datetime" json:"paid_at,omitempty"`
// Relationships
Account Account `gorm:"foreignKey:AccountID" json:"account,omitempty"`
RepaymentPlan *RepaymentPlan `gorm:"foreignKey:BillID" json:"repayment_plan,omitempty"`
}
// TableName specifies the table name for CreditCardBill
func (CreditCardBill) TableName() string {
return "credit_card_bills"
}
// RepaymentPlanStatus represents the status of a repayment plan
type RepaymentPlanStatus string
const (
RepaymentPlanStatusActive RepaymentPlanStatus = "active" // Plan is active
RepaymentPlanStatusCompleted RepaymentPlanStatus = "completed" // Plan completed
RepaymentPlanStatusCancelled RepaymentPlanStatus = "cancelled" // Plan cancelled
)
// RepaymentPlan represents a plan for repaying a credit card bill in installments
type RepaymentPlan struct {
BaseModel
UserID uint `gorm:"not null;index" json:"user_id"`
BillID uint `gorm:"not null;uniqueIndex" json:"bill_id"` // One plan per bill
TotalAmount float64 `gorm:"type:decimal(15,2);not null" json:"total_amount"`
RemainingAmount float64 `gorm:"type:decimal(15,2);not null" json:"remaining_amount"`
InstallmentCount int `gorm:"not null" json:"installment_count"`
InstallmentAmount float64 `gorm:"type:decimal(15,2);not null" json:"installment_amount"`
Status RepaymentPlanStatus `gorm:"size:20;not null;default:'active'" json:"status"`
// Relationships
Bill CreditCardBill `gorm:"foreignKey:BillID" json:"bill,omitempty"`
Installments []RepaymentInstallment `gorm:"foreignKey:PlanID" json:"installments,omitempty"`
}
// TableName specifies the table name for RepaymentPlan
func (RepaymentPlan) TableName() string {
return "repayment_plans"
}
// RepaymentInstallmentStatus represents the status of a repayment installment
type RepaymentInstallmentStatus string
const (
RepaymentInstallmentStatusPending RepaymentInstallmentStatus = "pending" // Not yet paid
RepaymentInstallmentStatusPaid RepaymentInstallmentStatus = "paid" // Paid
RepaymentInstallmentStatusOverdue RepaymentInstallmentStatus = "overdue" // Past due date
)
// RepaymentInstallment represents a single installment in a repayment plan
type RepaymentInstallment struct {
BaseModel
PlanID uint `gorm:"not null;index" json:"plan_id"`
DueDate time.Time `gorm:"type:date;not null;index" json:"due_date"`
Amount float64 `gorm:"type:decimal(15,2);not null" json:"amount"`
PaidAmount float64 `gorm:"type:decimal(15,2);default:0" json:"paid_amount"`
Status RepaymentInstallmentStatus `gorm:"size:20;not null;default:'pending'" json:"status"`
PaidAt *time.Time `gorm:"type:datetime" json:"paid_at,omitempty"`
Sequence int `gorm:"not null" json:"sequence"` // Installment number (1, 2, 3, ...)
// Relationships
Plan RepaymentPlan `gorm:"foreignKey:PlanID" json:"-"`
}
// TableName specifies the table name for RepaymentInstallment
func (RepaymentInstallment) TableName() string {
return "repayment_installments"
}
// PaymentReminder represents a reminder for upcoming payments
type PaymentReminder struct {
ID uint `gorm:"primarykey" json:"id"`
BillID uint `gorm:"not null;index" json:"bill_id"`
InstallmentID *uint `gorm:"index" json:"installment_id,omitempty"` // Optional, for installment reminders
ReminderDate time.Time `gorm:"type:date;not null;index" json:"reminder_date"`
Message string `gorm:"size:500;not null" json:"message"`
IsRead bool `gorm:"default:false" json:"is_read"`
CreatedAt time.Time `json:"created_at"`
// Relationships
Bill CreditCardBill `gorm:"foreignKey:BillID" json:"bill,omitempty"`
Installment *RepaymentInstallment `gorm:"foreignKey:InstallmentID" json:"installment,omitempty"`
}
// TableName specifies the table name for PaymentReminder
func (PaymentReminder) TableName() string {
return "payment_reminders"
}
// AppLock represents the application lock settings
type AppLock struct {
ID uint `gorm:"primarykey" json:"id"`
UserID uint `gorm:"not null;uniqueIndex" json:"user_id"`
PasswordHash string `gorm:"size:255;not null" json:"-"` // bcrypt hash of password
IsEnabled bool `gorm:"default:false" json:"is_enabled"`
FailedAttempts int `gorm:"default:0" json:"failed_attempts"`
LockedUntil *time.Time `gorm:"type:datetime" json:"locked_until,omitempty"`
LastFailedAttempt *time.Time `gorm:"type:datetime" json:"last_failed_attempt,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// User represents a user account for authentication
// Feature: api-interface-optimization
// Validates: Requirements 12, 13
type User struct {
ID uint `gorm:"primarykey" json:"id"`
Email string `gorm:"size:255;uniqueIndex" json:"email"`
PasswordHash string `gorm:"size:255" json:"-"`
Username string `gorm:"size:100" json:"username"`
Avatar string `gorm:"size:500" json:"avatar,omitempty"`
IsActive bool `gorm:"default:true" json:"is_active"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// Relationships
OAuthAccounts []OAuthAccount `gorm:"foreignKey:UserID" json:"oauth_accounts,omitempty"`
}
// TableName specifies the table name for User
func (User) TableName() string {
return "users"
}
// OAuthAccount represents an OAuth provider account linked to a user
// Feature: api-interface-optimization
// Validates: Requirements 13
type OAuthAccount struct {
ID uint `gorm:"primarykey" json:"id"`
UserID uint `gorm:"index" json:"user_id"`
Provider string `gorm:"size:50;index" json:"provider"` // github, google, etc.
ProviderID string `gorm:"size:255;index" json:"provider_id"`
AccessToken string `gorm:"size:500" json:"-"`
CreatedAt time.Time `json:"created_at"`
// Relationships
User User `gorm:"foreignKey:UserID" json:"-"`
}
// TableName specifies the table name for OAuthAccount
func (OAuthAccount) TableName() string {
return "oauth_accounts"
}
// TransactionTemplate represents a quick transaction template
// Feature: api-interface-optimization
// Validates: Requirements 15.1, 15.2
type TransactionTemplate struct {
ID uint `gorm:"primarykey" json:"id"`
UserID *uint `gorm:"index" json:"user_id,omitempty"`
Name string `gorm:"size:100;not null" json:"name"`
Amount float64 `gorm:"type:decimal(15,2)" json:"amount"`
Type TransactionType `gorm:"size:20;not null" json:"type"`
CategoryID uint `gorm:"not null" json:"category_id"`
AccountID uint `gorm:"not null" json:"account_id"`
Currency Currency `gorm:"size:10;not null;default:'CNY'" json:"currency"`
Note string `gorm:"size:500" json:"note,omitempty"`
SortOrder int `gorm:"default:0" json:"sort_order"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Relationships
Category Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
Account Account `gorm:"foreignKey:AccountID" json:"account,omitempty"`
}
// TableName specifies the table name for TransactionTemplate
func (TransactionTemplate) TableName() string {
return "transaction_templates"
}
// UserPreference represents user preferences for quick entry
// Feature: api-interface-optimization
// Validates: Requirements 15.4
type UserPreference struct {
ID uint `gorm:"primarykey" json:"id"`
UserID *uint `gorm:"uniqueIndex" json:"user_id,omitempty"`
LastAccountID *uint `gorm:"index" json:"last_account_id,omitempty"`
LastCategoryID *uint `gorm:"index" json:"last_category_id,omitempty"`
FrequentAccounts string `gorm:"size:500" json:"frequent_accounts,omitempty"` // JSON array of account IDs
FrequentCategories string `gorm:"size:500" json:"frequent_categories,omitempty"` // JSON array of category IDs
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// TableName specifies the table name for UserPreference
func (UserPreference) TableName() string {
return "user_preferences"
}
// TableName specifies the table name for AppLock
func (AppLock) TableName() string {
return "app_locks"
}
// IsLocked returns true if the app is currently locked due to failed attempts
func (a *AppLock) IsLocked() bool {
if a.LockedUntil == nil {
return false
}
return time.Now().Before(*a.LockedUntil)
}
// AllocationRecord represents a record of income allocation execution
type AllocationRecord struct {
ID uint `gorm:"primarykey" json:"id"`
UserID uint `gorm:"not null;index" json:"user_id"`
RuleID uint `gorm:"not null;index" json:"rule_id"`
RuleName string `gorm:"size:100;not null" json:"rule_name"`
SourceAccountID uint `gorm:"not null;index" json:"source_account_id"`
TotalAmount float64 `gorm:"type:decimal(15,2);not null" json:"total_amount"`
AllocatedAmount float64 `gorm:"type:decimal(15,2);not null" json:"allocated_amount"`
RemainingAmount float64 `gorm:"type:decimal(15,2);not null" json:"remaining_amount"`
Note string `gorm:"size:500" json:"note,omitempty"`
CreatedAt time.Time `json:"created_at"`
// Relationships
Rule AllocationRule `gorm:"foreignKey:RuleID" json:"rule,omitempty"`
SourceAccount Account `gorm:"foreignKey:SourceAccountID" json:"source_account,omitempty"`
Details []AllocationRecordDetail `gorm:"foreignKey:RecordID" json:"details,omitempty"`
}
// TableName specifies the table name for AllocationRecord
func (AllocationRecord) TableName() string {
return "allocation_records"
}
// AllocationRecordDetail represents a single allocation detail in a record
type AllocationRecordDetail struct {
ID uint `gorm:"primarykey" json:"id"`
RecordID uint `gorm:"not null;index" json:"record_id"`
TargetType TargetType `gorm:"size:20;not null" json:"target_type"`
TargetID uint `gorm:"not null" json:"target_id"`
TargetName string `gorm:"size:100;not null" json:"target_name"`
Amount float64 `gorm:"type:decimal(15,2);not null" json:"amount"`
Percentage *float64 `gorm:"type:decimal(5,2)" json:"percentage,omitempty"`
FixedAmount *float64 `gorm:"type:decimal(15,2)" json:"fixed_amount,omitempty"`
// Relationships
Record AllocationRecord `gorm:"foreignKey:RecordID" json:"-"`
}
// TableName specifies the table name for AllocationRecordDetail
func (AllocationRecordDetail) TableName() string {
return "allocation_record_details"
}
// AllModels returns all models for database migration
func AllModels() []interface{} {
return []interface{}{
&Account{},
&Category{},
&Tag{},
&Transaction{},
&TransactionTag{}, // Explicit join table for many-to-many relationship
&Budget{},
&PiggyBank{},
&RecurringTransaction{},
&AllocationRule{},
&AllocationTarget{},
&AllocationRecord{},
&AllocationRecordDetail{},
&ExchangeRate{},
&ClassificationRule{},
&CreditCardBill{},
&RepaymentPlan{},
&RepaymentInstallment{},
&PaymentReminder{},
&AppLock{},
&User{},
&OAuthAccount{},
&TransactionTemplate{},
&UserPreference{},
&Ledger{}, // Feature: accounting-feature-upgrade
&SystemCategory{}, // Feature: accounting-feature-upgrade
&TransactionImage{}, // Feature: accounting-feature-upgrade
&UserSettings{}, // Feature: accounting-feature-upgrade
}
}
// IsCreditAccountType returns true if the account type supports negative balance
func IsCreditAccountType(accountType AccountType) bool {
return accountType == AccountTypeCreditCard || accountType == AccountTypeCreditLine
}
// CurrencyInfo contains display information for a currency
type CurrencyInfo struct {
Code Currency `json:"code"`
Name string `json:"name"`
Symbol string `json:"symbol"`
}
// GetCurrencyInfo returns display information for all supported currencies
func GetCurrencyInfo() []CurrencyInfo {
return []CurrencyInfo{
// Major currencies
{Code: CurrencyCNY, Name: "人民币", Symbol: "¥"},
{Code: CurrencyUSD, Name: "美元", Symbol: "$"},
{Code: CurrencyEUR, Name: "欧元", Symbol: "€"},
{Code: CurrencyJPY, Name: "日元", Symbol: "¥"},
{Code: CurrencyGBP, Name: "英镑", Symbol: "£"},
{Code: CurrencyHKD, Name: "港币", Symbol: "HK$"},
// Asia Pacific
{Code: CurrencyAUD, Name: "澳元", Symbol: "A$"},
{Code: CurrencyNZD, Name: "新西兰元", Symbol: "NZ$"},
{Code: CurrencySGD, Name: "新加坡元", Symbol: "S$"},
{Code: CurrencyKRW, Name: "韩元", Symbol: "₩"},
{Code: CurrencyTHB, Name: "泰铢", Symbol: "฿"},
{Code: CurrencyTWD, Name: "新台币", Symbol: "NT$"},
{Code: CurrencyMOP, Name: "澳门元", Symbol: "MOP$"},
{Code: CurrencyPHP, Name: "菲律宾比索", Symbol: "₱"},
{Code: CurrencyIDR, Name: "印尼盾", Symbol: "Rp"},
{Code: CurrencyINR, Name: "印度卢比", Symbol: "₹"},
{Code: CurrencyVND, Name: "越南盾", Symbol: "₫"},
{Code: CurrencyMNT, Name: "蒙古图格里克", Symbol: "₮"},
{Code: CurrencyKHR, Name: "柬埔寨瑞尔", Symbol: "៛"},
{Code: CurrencyNPR, Name: "尼泊尔卢比", Symbol: "₨"},
{Code: CurrencyPKR, Name: "巴基斯坦卢比", Symbol: "₨"},
{Code: CurrencyBND, Name: "文莱元", Symbol: "B$"},
// Europe
{Code: CurrencyCHF, Name: "瑞士法郎", Symbol: "CHF"},
{Code: CurrencySEK, Name: "瑞典克朗", Symbol: "kr"},
{Code: CurrencyNOK, Name: "挪威克朗", Symbol: "kr"},
{Code: CurrencyDKK, Name: "丹麦克朗", Symbol: "kr"},
{Code: CurrencyCZK, Name: "捷克克朗", Symbol: "Kč"},
{Code: CurrencyHUF, Name: "匈牙利福林", Symbol: "Ft"},
{Code: CurrencyRUB, Name: "俄罗斯卢布", Symbol: "₽"},
{Code: CurrencyTRY, Name: "土耳其里拉", Symbol: "₺"},
// Americas
{Code: CurrencyCAD, Name: "加元", Symbol: "C$"},
{Code: CurrencyMXN, Name: "墨西哥比索", Symbol: "Mex$"},
{Code: CurrencyBRL, Name: "巴西雷亚尔", Symbol: "R$"},
// Middle East & Africa
{Code: CurrencyAED, Name: "阿联酋迪拉姆", Symbol: "د.إ"},
{Code: CurrencySAR, Name: "沙特里亚尔", Symbol: "﷼"},
{Code: CurrencyQAR, Name: "卡塔尔里亚尔", Symbol: "﷼"},
{Code: CurrencyKWD, Name: "科威特第纳尔", Symbol: "د.ك"},
{Code: CurrencyILS, Name: "以色列新谢克尔", Symbol: "₪"},
{Code: CurrencyZAR, Name: "南非兰特", Symbol: "R"},
}
}