Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions packages/backend/app/db/migrations/004_savings_goals.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-- Migration 004: Goal-based savings tracking & milestones
-- Issue #133
-- Apply with: psql $DATABASE_URL -f migrations/004_savings_goals.sql

CREATE TABLE IF NOT EXISTS savings_goals (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(200) NOT NULL,
description VARCHAR(500) NULL,
target_amount NUMERIC(12,2) NOT NULL,
current_amount NUMERIC(12,2) NOT NULL DEFAULT 0,
currency VARCHAR(10) NOT NULL DEFAULT 'INR',
target_date DATE NULL,
achieved BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS savings_milestones (
id SERIAL PRIMARY KEY,
goal_id INTEGER NOT NULL REFERENCES savings_goals(id) ON DELETE CASCADE,
name VARCHAR(200) NOT NULL,
target_amount NUMERIC(12,2) NOT NULL,
achieved BOOLEAN NOT NULL DEFAULT FALSE,
achieved_at TIMESTAMP NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_savings_goals_user ON savings_goals (user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_savings_milestones_goal ON savings_milestones (goal_id, target_amount);
36 changes: 36 additions & 0 deletions packages/backend/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,39 @@ class AuditLog(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
action = db.Column(db.String(100), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)


# ── Savings Goals ──────────────────────────────────────────────────────────────

class SavingsGoal(db.Model):
"""A financial savings goal with optional milestones."""

__tablename__ = "savings_goals"
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
name = db.Column(db.String(200), nullable=False)
description = db.Column(db.String(500), nullable=True)
target_amount = db.Column(db.Numeric(12, 2), nullable=False)
current_amount = db.Column(db.Numeric(12, 2), default=0, nullable=False)
currency = db.Column(db.String(10), default="INR", nullable=False)
target_date = db.Column(db.Date, nullable=True)
achieved = db.Column(db.Boolean, default=False, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)

milestones = db.relationship("SavingsMilestone", back_populates="goal", cascade="all, delete-orphan", order_by="SavingsMilestone.target_amount")


class SavingsMilestone(db.Model):
"""A milestone (sub-target) within a savings goal."""

__tablename__ = "savings_milestones"
id = db.Column(db.Integer, primary_key=True)
goal_id = db.Column(db.Integer, db.ForeignKey("savings_goals.id"), nullable=False)
name = db.Column(db.String(200), nullable=False)
target_amount = db.Column(db.Numeric(12, 2), nullable=False)
achieved = db.Column(db.Boolean, default=False, nullable=False)
achieved_at = db.Column(db.DateTime, nullable=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)

goal = db.relationship("SavingsGoal", back_populates="milestones")
5 changes: 5 additions & 0 deletions packages/backend/app/models_savings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
Savings Goals models — appended to models.py via import.
Defined separately to keep the diff clean.
"""
# This file is intentionally empty — models are added directly to models.py
2 changes: 2 additions & 0 deletions packages/backend/app/routes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .categories import bp as categories_bp
from .docs import bp as docs_bp
from .dashboard import bp as dashboard_bp
from .savings import bp as savings_bp


def register_routes(app: Flask):
Expand All @@ -18,3 +19,4 @@ def register_routes(app: Flask):
app.register_blueprint(categories_bp, url_prefix="/categories")
app.register_blueprint(docs_bp, url_prefix="/docs")
app.register_blueprint(dashboard_bp, url_prefix="/dashboard")
app.register_blueprint(savings_bp, url_prefix="/savings")
Loading