936 lines
37 KiB
Go
936 lines
37 KiB
Go
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"},
|
||
}
|
||
}
|