first commit

This commit is contained in:
Matthias Hinrichs
2025-08-26 03:17:49 +02:00
commit 189e7a2329
34 changed files with 8835 additions and 0 deletions
+336
View File
@@ -0,0 +1,336 @@
package models
import (
"time"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
// User repräsentiert einen Benutzer des Systems
type User struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `json:"name" gorm:"not null"`
Email string `json:"email" gorm:"uniqueIndex;not null"`
PasswordHash string `json:"-" gorm:"not null"` // JSON-Tag "-" versteckt das Feld in API-Responses
BankAccounts []BankAccount `gorm:"foreignKey:UserID" json:"bank_accounts,omitempty"`
Depots []Depot `gorm:"foreignKey:UserID" json:"depots,omitempty"`
Transactions []Transaction `gorm:"foreignKey:UserID" json:"transactions,omitempty"`
Categories []Category `gorm:"foreignKey:UserID" json:"categories,omitempty"`
RecurrenceRules []RecurrenceRule `gorm:"foreignKey:UserID" json:"recurrence_rules,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// BankAccount repräsentiert ein Bankkonto eines Benutzers
type BankAccount struct {
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `json:"user_id" gorm:"not null"`
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
Name string `json:"name" gorm:"not null"`
Bank string `json:"bank" gorm:"not null"`
IBAN string `json:"iban"`
Balance float64 `json:"balance" gorm:"default:0"`
AccountType string `json:"account_type" gorm:"not null"` // "checking", "savings", "credit"
Transactions []Transaction `gorm:"foreignKey:BankAccountID" json:"transactions,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Depot repräsentiert ein Wertpapierdepot eines Benutzers
type Depot struct {
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `json:"user_id" gorm:"not null"`
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
Name string `json:"name" gorm:"not null"`
Broker string `json:"broker" gorm:"not null"`
DepotNumber string `json:"depot_number"`
TotalValue float64 `json:"total_value" gorm:"default:0"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Category repräsentiert eine Transaktionskategorie
type Category struct {
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `json:"user_id" gorm:"not null"`
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
Name string `json:"name" gorm:"not null"`
Description string `json:"description"`
Color string `json:"color" gorm:"default:'#6B7280'"` // Default-Farbe
Icon string `json:"icon" gorm:"default:'💰'"` // Default-Icon
Transactions []Transaction `gorm:"foreignKey:CategoryID" json:"transactions,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Transaction repräsentiert eine Finanztransaktion
type Transaction struct {
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `json:"user_id" gorm:"not null"`
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
BankAccountID *uint `json:"bank_account_id"` // Optional: kann NULL sein für Depot-Transaktionen
BankAccount *BankAccount `gorm:"foreignKey:BankAccountID" json:"bank_account,omitempty"`
CategoryID *uint `json:"category_id"` // Optional
Category *Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
Amount float64 `json:"amount" gorm:"not null"`
Description string `json:"description" gorm:"not null"`
Type string `json:"type" gorm:"not null"` // "income" oder "expense"
Date time.Time `json:"date" gorm:"not null"`
IsRecurring bool `json:"is_recurring" gorm:"default:false"`
RecurrenceRuleID *uint `json:"recurrence_rule_id"` // Optional: Link zur Wiederholungsregel
RecurrenceRule *RecurrenceRule `gorm:"foreignKey:RecurrenceRuleID" json:"recurrence_rule,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// RecurrenceRule definiert die Regeln für wiederkehrende Transaktionen
type RecurrenceRule struct {
ID uint `gorm:"primaryKey" json:"id"`
UserID uint `json:"user_id" gorm:"not null"`
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
// Template-Informationen für neue Transaktionen
Amount float64 `json:"amount" gorm:"not null"`
Description string `json:"description" gorm:"not null"`
CategoryID *uint `json:"category_id"`
Category *Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"`
Type string `json:"type" gorm:"not null"` // "income" or "expense"
BankAccountID *uint `json:"bank_account_id"`
BankAccount *BankAccount `gorm:"foreignKey:BankAccountID" json:"bank_account,omitempty"`
// Wiederholungsregeln
Interval string `json:"interval" gorm:"not null"` // "daily", "weekly", "monthly", "yearly"
IntervalCount int `json:"interval_count" gorm:"default:1"` // z.B. alle 2 Wochen = weekly + 2
StartDate time.Time `json:"start_date" gorm:"not null"`
EndDate *time.Time `json:"end_date"` // Optional: NULL bedeutet unbegrenzt
DayOfWeek *int `json:"day_of_week"` // 0=Sonntag, 1=Montag, etc. (für wöchentlich)
DayOfMonth *int `json:"day_of_month"` // 1-31 (für monatlich)
IsActive bool `json:"is_active" gorm:"default:true"`
LastGenerated *time.Time `json:"last_generated"` // Letztes generiertes Datum
// Beziehungen zu generierten Transaktionen
Transactions []Transaction `gorm:"foreignKey:RecurrenceRuleID" json:"transactions,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Dashboard-spezifische Strukturen für Auswertungen
// AssetOverview stellt eine Vermögensübersicht dar
type AssetOverview struct {
TotalBankBalance float64 `json:"total_bank_balance"`
TotalDepotValue float64 `json:"total_depot_value"`
TotalAssets float64 `json:"total_assets"`
BankAccounts []BankAccountSummary `json:"bank_accounts"`
Depots []DepotSummary `json:"depots"`
}
// BankAccountSummary für Dashboard-Übersicht
type BankAccountSummary struct {
ID uint `json:"id"`
Name string `json:"name"`
Bank string `json:"bank"`
AccountType string `json:"account_type"`
Balance float64 `json:"balance"`
}
// DepotSummary für Dashboard-Übersicht
type DepotSummary struct {
ID uint `json:"id"`
Name string `json:"name"`
Broker string `json:"broker"`
TotalValue float64 `json:"total_value"`
}
// MonthlyStats für monatliche Statistiken
type MonthlyStats struct {
Month string `json:"month"`
Income float64 `json:"income"`
Expenses float64 `json:"expenses"`
NetChange float64 `json:"net_change"`
Balance float64 `json:"balance"`
}
// WeeklyStats für wöchentliche Statistiken
type WeeklyStats struct {
Week string `json:"week"`
Income float64 `json:"income"`
Expenses float64 `json:"expenses"`
NetChange float64 `json:"net_change"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
}
// CategoryStats für Kategorie-Auswertungen
type CategoryStats struct {
CategoryID uint `json:"category_id"`
CategoryName string `json:"category_name"`
TotalAmount float64 `json:"total_amount"`
Count int `json:"count"`
Percentage float64 `json:"percentage"`
}
// TransactionTrend für Verlaufsdaten
type TransactionTrend struct {
Date time.Time `json:"date"`
Income float64 `json:"income"`
Expense float64 `json:"expense"`
Balance float64 `json:"balance"`
}
// DashboardData kombiniert alle Dashboard-Daten
type DashboardData struct {
UserName string `json:"user_name"`
AssetOverview AssetOverview `json:"asset_overview"`
MonthlyStats []MonthlyStats `json:"monthly_stats"`
WeeklyStats []WeeklyStats `json:"weekly_stats"`
CategoryStats []CategoryStats `json:"category_stats"`
TransactionTrend []TransactionTrend `json:"transaction_trend"`
RecentTransactions []Transaction `json:"recent_transactions"`
}
// HashPassword erstellt einen bcrypt Hash des Passworts
func (u *User) HashPassword(password string) error {
hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
u.PasswordHash = string(hashedBytes)
return nil
}
// CheckPassword vergleicht das eingegebene Passwort mit dem Hash
func (u *User) CheckPassword(password string) bool {
err := bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password))
return err == nil
}
// IsRecurringTransaction prüft, ob eine Transaktion wiederkehrend ist
func (t *Transaction) IsRecurringTransaction() bool {
return t.IsRecurring && t.RecurrenceRule != nil
}
// GetNextOccurrence berechnet das nächste Auftreten einer wiederkehrenden Transaktion
func (r *RecurrenceRule) GetNextOccurrence(from time.Time) *time.Time {
if !r.IsActive {
return nil
}
var next time.Time
switch r.Interval {
case "daily":
next = from.AddDate(0, 0, r.IntervalCount)
case "weekly":
next = from.AddDate(0, 0, 7*r.IntervalCount)
// Wenn ein bestimmter Wochentag gewünscht ist
if r.DayOfWeek != nil {
daysUntilTargetDay := (*r.DayOfWeek - int(from.Weekday()) + 7) % 7
if daysUntilTargetDay == 0 && from.Equal(r.StartDate) {
daysUntilTargetDay = 7 * r.IntervalCount
}
next = from.AddDate(0, 0, daysUntilTargetDay)
}
case "monthly":
next = from.AddDate(0, r.IntervalCount, 0)
// Wenn ein bestimmter Tag im Monat gewünscht ist
if r.DayOfMonth != nil && *r.DayOfMonth >= 1 && *r.DayOfMonth <= 31 {
next = time.Date(next.Year(), next.Month(), *r.DayOfMonth, next.Hour(), next.Minute(), next.Second(), next.Nanosecond(), next.Location())
}
case "yearly":
next = from.AddDate(r.IntervalCount, 0, 0)
default:
return nil
}
// Prüfen, ob das nächste Datum nach dem Enddatum liegt
if r.EndDate != nil && next.After(*r.EndDate) {
return nil
}
return &next
}
// CanGenerate prüft, ob eine neue Instanz der wiederkehrenden Transaktion generiert werden kann
func (r *RecurrenceRule) CanGenerate() bool {
if !r.IsActive {
return false
}
now := time.Now()
// Prüfen, ob wir noch vor dem Enddatum sind
if r.EndDate != nil && now.After(*r.EndDate) {
return false
}
// Prüfen, ob es Zeit für die nächste Generierung ist
lastDate := r.StartDate
if r.LastGenerated != nil {
lastDate = *r.LastGenerated
}
nextOccurrence := r.GetNextOccurrence(lastDate)
if nextOccurrence == nil {
return false
}
// Geändert: Generiere Transaktionen für die nächsten 12 Monate (für Dashboard-Trend)
future12Months := now.AddDate(1, 0, 0)
if nextOccurrence.After(future12Months) {
return false
}
return true
}
// GenerateTransaction erstellt eine neue Transaktion basierend auf der Wiederholungsregel
func (r *RecurrenceRule) GenerateTransaction(db *gorm.DB) (*Transaction, error) {
if !r.CanGenerate() {
return nil, nil // Keine Transaktion zu generieren
}
// Nächstes Datum berechnen
lastDate := r.StartDate
if r.LastGenerated != nil {
lastDate = *r.LastGenerated
}
nextDate := r.GetNextOccurrence(lastDate)
if nextDate == nil {
return nil, nil
}
// Neue Transaktion erstellen
transaction := &Transaction{
UserID: r.UserID,
BankAccountID: r.BankAccountID,
Amount: r.Amount,
Description: r.Description,
CategoryID: r.CategoryID,
Type: r.Type,
Date: *nextDate,
IsRecurring: true,
RecurrenceRuleID: &r.ID,
}
// Transaktion in der Datenbank speichern
if err := db.Create(transaction).Error; err != nil {
return nil, err
}
// LastGenerated aktualisieren
r.LastGenerated = nextDate
if err := db.Save(r).Error; err != nil {
return nil, err
}
return transaction, nil
}
// Migrate führt die Datenbank-Migration durch
func Migrate(db *gorm.DB) error {
return db.AutoMigrate(&User{}, &Category{}, &Transaction{}, &BankAccount{}, &Depot{}, &RecurrenceRule{})
}