diff --git a/.agents/skills/commit/SKILL.md b/.agents/skills/commit/SKILL.md index e9858756..a0ab7154 100644 --- a/.agents/skills/commit/SKILL.md +++ b/.agents/skills/commit/SKILL.md @@ -13,6 +13,9 @@ description: 提交並推送程式碼變更。用於每個開發階段完成後 - 要把程式碼推送到 GitHub,讓組員可以取得最新版本 - 遇到 Git 要求設定 `user.name` 或 `user.email` 的提示 +## 檢查 .gitignore +在推送之前檢查 .gitignore,確保虛擬環境、環境設定與非必要檔案有正確被排除在版本控制之外。 + ## ⚠️ 設定 Git 使用者身份 如果 Git 顯示以下提示,需要先設定身份才能 commit: diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..f9dfb12d --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +FLASK_APP=app.py +FLASK_DEBUG=1 +SECRET_KEY=super-secret-key-change-in-production diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8760da8c --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Environment +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# SQLite database and instance directory +instance/ +*.db +*.sqlite3 + +# OS generated files +.DS_Store +Thumbs.db diff --git a/README.md b/README.md index 62cd639b..cc3edf2d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ - [系統簡介](#系統簡介) - [技術棧](#技術棧) -- [組員與分工](#組員與分工) +- [系統截圖](#系統截圖) - [個人心得](#個人心得) --- @@ -25,13 +25,8 @@ --- -## 組員與分工(先寫自己就好) - -**第 X 組** - -| 姓名 | 學號 | 負責部分 | -| ---- | ---- | -------- | -| | | | +## 系統截圖 +請在此貼上系統的截圖畫面, 選擇一個功能邊操作邊截圖 --- diff --git a/app.py b/app.py new file mode 100644 index 00000000..2fd50013 --- /dev/null +++ b/app.py @@ -0,0 +1,30 @@ +import os +from flask import Flask +from dotenv import load_dotenv + +# 載入環境變數 +load_dotenv() + +def create_app(): + app = Flask(__name__, template_folder='app/templates') + app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'default-dev-key') + + # 載入並註冊 Blueprints + from app.routes.main import main_bp + from app.routes.transaction import transaction_bp + from app.routes.fixed_deduction import fixed_deduction_bp + + app.register_blueprint(main_bp) + app.register_blueprint(transaction_bp) + app.register_blueprint(fixed_deduction_bp) + + # 初始化資料庫 + from app.models.db import init_db + init_db() + + return app + +app = create_app() + +if __name__ == '__main__': + app.run(debug=True) diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 00000000..32948706 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1 @@ +# 標記 app 為 Python 套件 diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 00000000..f01acc75 --- /dev/null +++ b/app/models/__init__.py @@ -0,0 +1 @@ +# initialize app/models module diff --git a/app/models/db.py b/app/models/db.py new file mode 100644 index 00000000..b09fcde1 --- /dev/null +++ b/app/models/db.py @@ -0,0 +1,21 @@ +import sqlite3 +import os + +DB_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'instance', 'database.db') + +def get_db_connection(): + """建立並回傳資料庫連線,設定 row_factory 讓結果可透過字典的 key 來存取。""" + os.makedirs(os.path.dirname(DB_PATH), exist_ok=True) + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + return conn + +def init_db(): + """初始化資料庫實體,執行 schema.sql。""" + schema_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'database', 'schema.sql') + if not os.path.exists(schema_path): + return + with open(schema_path, 'r', encoding='utf-8') as f: + schema = f.read() + with get_db_connection() as conn: + conn.executescript(schema) diff --git a/app/models/fixed_deduction.py b/app/models/fixed_deduction.py new file mode 100644 index 00000000..e230eeae --- /dev/null +++ b/app/models/fixed_deduction.py @@ -0,0 +1,107 @@ +import sqlite3 +from app.models.db import get_db_connection + +class FixedDeduction: + @staticmethod + def create(amount, category, deduct_day): + """ + 新增一筆記錄 + :param amount: 扣款金額 + :param category: 扣款分類 + :param deduct_day: 每月固定扣款日 (1-31) + :return: 新增成功的紀錄 ID,若失敗則回傳 None + """ + try: + with get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute( + "INSERT INTO fixed_deductions (amount, category, deduct_day) VALUES (?, ?, ?)", + (amount, category, deduct_day) + ) + conn.commit() + return cursor.lastrowid + except sqlite3.Error as e: + print(f"Database error in FixedDeduction.create: {e}") + return None + + @staticmethod + def get_all(): + """ + 取得所有記錄 + :return: 紀錄列表 (sqlite3.Row 格式) + """ + try: + with get_db_connection() as conn: + return conn.execute("SELECT * FROM fixed_deductions ORDER BY deduct_day ASC").fetchall() + except sqlite3.Error as e: + print(f"Database error in FixedDeduction.get_all: {e}") + return [] + + @staticmethod + def get_by_id(deduction_id): + """ + 取得單筆記錄 + :param deduction_id: FixedDeduction ID + :return: 該筆記錄 (sqlite3.Row) 或 None + """ + try: + with get_db_connection() as conn: + return conn.execute("SELECT * FROM fixed_deductions WHERE id = ?", (deduction_id,)).fetchone() + except sqlite3.Error as e: + print(f"Database error in FixedDeduction.get_by_id: {e}") + return None + + @staticmethod + def update(deduction_id, amount, category, deduct_day): + """ + 更新記錄 + :param deduction_id: FixedDeduction ID + :return: 成功與否的布林值 + """ + try: + with get_db_connection() as conn: + conn.execute( + "UPDATE fixed_deductions SET amount=?, category=?, deduct_day=? WHERE id=?", + (amount, category, deduct_day, deduction_id) + ) + conn.commit() + return True + except sqlite3.Error as e: + print(f"Database error in FixedDeduction.update: {e}") + return False + + @staticmethod + def update_last_processed(deduction_id, processed_month): + """ + 更新記錄最後被處理的月份 + :param deduction_id: FixedDeduction ID + :param processed_month: 處理的月份字串 (YYYY-MM) + :return: 成功與否的布林值 + """ + try: + with get_db_connection() as conn: + conn.execute( + "UPDATE fixed_deductions SET last_processed_month = ? WHERE id = ?", + (processed_month, deduction_id) + ) + conn.commit() + return True + except sqlite3.Error as e: + print(f"Database error in FixedDeduction.update_last_processed: {e}") + return False + + @staticmethod + def delete(deduction_id): + """ + 刪除記錄 + :param deduction_id: FixedDeduction ID + :return: 成功與否的布林值 + """ + try: + with get_db_connection() as conn: + conn.execute("DELETE FROM fixed_deductions WHERE id = ?", (deduction_id,)) + conn.commit() + return True + except sqlite3.Error as e: + print(f"Database error in FixedDeduction.delete: {e}") + return False diff --git a/app/models/recipe.py b/app/models/recipe.py new file mode 100644 index 00000000..4102d3fa --- /dev/null +++ b/app/models/recipe.py @@ -0,0 +1,73 @@ +import sqlite3 +import os +from contextlib import contextmanager + +# 取得資料庫檔案存放的目錄 instance/ (位於專案根目錄) +DB_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'instance') +DB_PATH = os.path.join(DB_DIR, 'database.db') + +@contextmanager +def get_db_connection(): + # 若目錄不存在則建立一個 + os.makedirs(DB_DIR, exist_ok=True) + conn = sqlite3.connect(DB_PATH) + # 將回傳結果設為 dict-like 的 Row 物件 + conn.row_factory = sqlite3.Row + try: + yield conn + finally: + conn.commit() + conn.close() + +class Recipe: + @staticmethod + def create(data): + with get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute(''' + INSERT INTO recipes (title, description, ingredients, steps) + VALUES (?, ?, ?, ?) + ''', (data['title'], data.get('description', ''), data['ingredients'], data['steps'])) + return cursor.lastrowid + + @staticmethod + def get_all(query=None): + with get_db_connection() as conn: + cursor = conn.cursor() + if query: + # 簡單支援對 title 或 ingredients 的 LIKE 搜尋 (搜尋推薦食譜) + search_term = f"%{query}%" + cursor.execute(''' + SELECT * FROM recipes + WHERE title LIKE ? OR ingredients LIKE ? + ORDER BY created_at DESC + ''', (search_term, search_term)) + else: + cursor.execute('SELECT * FROM recipes ORDER BY created_at DESC') + return [dict(row) for row in cursor.fetchall()] + + @staticmethod + def get_by_id(recipe_id): + with get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute('SELECT * FROM recipes WHERE id = ?', (recipe_id,)) + row = cursor.fetchone() + return dict(row) if row else None + + @staticmethod + def update(recipe_id, data): + with get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute(''' + UPDATE recipes + SET title = ?, description = ?, ingredients = ?, steps = ?, updated_at = CURRENT_TIMESTAMP + WHERE id = ? + ''', (data['title'], data.get('description', ''), data['ingredients'], data['steps'], recipe_id)) + return cursor.rowcount > 0 + + @staticmethod + def delete(recipe_id): + with get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute('DELETE FROM recipes WHERE id = ?', (recipe_id,)) + return cursor.rowcount > 0 diff --git a/app/models/transaction.py b/app/models/transaction.py new file mode 100644 index 00000000..a0d21ade --- /dev/null +++ b/app/models/transaction.py @@ -0,0 +1,129 @@ +import sqlite3 +from app.models.db import get_db_connection + +class Transaction: + @staticmethod + def create(tx_type, amount, category, transaction_date): + """ + 新增一筆記錄 + :param tx_type: 'INCOME' 或 'EXPENSE' + :param amount: 金額 + :param category: 分類 + :param transaction_date: 交易日期 (YYYY-MM-DD) + :return: 新增成功的紀錄 ID,若失敗則回傳 None + """ + try: + with get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute( + "INSERT INTO transactions (type, amount, category, transaction_date) VALUES (?, ?, ?, ?)", + (tx_type, amount, category, transaction_date) + ) + conn.commit() + return cursor.lastrowid + except sqlite3.Error as e: + print(f"Database error in Transaction.create: {e}") + return None + + @staticmethod + def get_all(): + """ + 取得所有記錄 + :return: 紀錄列表 (sqlite3.Row 格式) + """ + try: + with get_db_connection() as conn: + return conn.execute( + "SELECT * FROM transactions ORDER BY transaction_date DESC, created_at DESC" + ).fetchall() + except sqlite3.Error as e: + print(f"Database error in Transaction.get_all: {e}") + return [] + + @staticmethod + def get_by_date_range(start_date, end_date): + """ + 取得指定日期區間的紀錄 + :return: 紀錄列表 + """ + try: + with get_db_connection() as conn: + return conn.execute( + "SELECT * FROM transactions WHERE transaction_date >= ? AND transaction_date <= ? ORDER BY transaction_date DESC, created_at DESC", + (start_date, end_date) + ).fetchall() + except sqlite3.Error as e: + print(f"Database error in Transaction.get_by_date_range: {e}") + return [] + + @staticmethod + def get_by_id(tx_id): + """ + 取得單筆記錄 + :param tx_id: Transaction ID + :return: 該筆記錄 (sqlite3.Row) 或 None + """ + try: + with get_db_connection() as conn: + return conn.execute("SELECT * FROM transactions WHERE id = ?", (tx_id,)).fetchone() + except sqlite3.Error as e: + print(f"Database error in Transaction.get_by_id: {e}") + return None + + @staticmethod + def update(tx_id, tx_type, amount, category, transaction_date): + """ + 更新記錄 + :param tx_id: Transaction ID + :return: 成功與否的布林值 + """ + try: + with get_db_connection() as conn: + conn.execute( + "UPDATE transactions SET type=?, amount=?, category=?, transaction_date=? WHERE id=?", + (tx_type, amount, category, transaction_date, tx_id) + ) + conn.commit() + return True + except sqlite3.Error as e: + print(f"Database error in Transaction.update: {e}") + return False + + @staticmethod + def delete(tx_id): + """ + 刪除記錄 + :param tx_id: Transaction ID + :return: 成功與否的布林值 + """ + try: + with get_db_connection() as conn: + conn.execute("DELETE FROM transactions WHERE id = ?", (tx_id,)) + conn.commit() + return True + except sqlite3.Error as e: + print(f"Database error in Transaction.delete: {e}") + return False + + @staticmethod + def get_total_balance(): + """ + 取得當前總餘額 + :return: 總餘額數值 + """ + try: + with get_db_connection() as conn: + # 簡化計算方式,取收入總和與支出總和 + result = conn.execute(""" + SELECT + SUM(CASE WHEN type='INCOME' THEN amount ELSE 0 END) as total_income, + SUM(CASE WHEN type='EXPENSE' THEN amount ELSE 0 END) as total_expense + FROM transactions + """).fetchone() + + income = result['total_income'] if result['total_income'] else 0 + expense = result['total_expense'] if result['total_expense'] else 0 + return income - expense + except sqlite3.Error as e: + print(f"Database error in Transaction.get_total_balance: {e}") + return 0 diff --git a/app/routes/__init__.py b/app/routes/__init__.py new file mode 100644 index 00000000..d1a57de5 --- /dev/null +++ b/app/routes/__init__.py @@ -0,0 +1,2 @@ +# init routes +from app.routes.recipe import recipe_bp diff --git a/app/routes/fixed_deduction.py b/app/routes/fixed_deduction.py new file mode 100644 index 00000000..36ae985e --- /dev/null +++ b/app/routes/fixed_deduction.py @@ -0,0 +1,50 @@ +from flask import Blueprint, request, redirect, render_template, flash, url_for +from app.models.fixed_deduction import FixedDeduction + +fixed_deduction_bp = Blueprint('fixed_deduction', __name__, url_prefix='/fixed-deductions') + +@fixed_deduction_bp.route('/', methods=['GET']) +def index(): + """檢視所有的每月固定扣款清單""" + deductions = FixedDeduction.get_all() + return render_template('fixed_deductions/index.html', deductions=deductions) + +@fixed_deduction_bp.route('/new', methods=['GET']) +def new_fixed_deduction(): + """顯示新增固定扣款的表單介面""" + return render_template('fixed_deductions/form.html') + +@fixed_deduction_bp.route('/', methods=['POST']) +def create_fixed_deduction(): + """寫入新的固定扣款設定""" + amount_str = request.form.get('amount') + category = request.form.get('category') + deduct_day_str = request.form.get('deduct_day') + + if not amount_str or not category or not deduct_day_str: + flash('請填寫所有欄位') + return redirect(url_for('fixed_deduction.new_fixed_deduction')) + + try: + amount = int(amount_str) + deduct_day = int(deduct_day_str) + if amount <= 0: + flash('金額必須為正整數') + return redirect(url_for('fixed_deduction.new_fixed_deduction')) + if deduct_day < 1 or deduct_day > 31: + flash('扣款日必須在 1 到 31 之間') + return redirect(url_for('fixed_deduction.new_fixed_deduction')) + except ValueError: + flash('金額與日期必須為正整數') + return redirect(url_for('fixed_deduction.new_fixed_deduction')) + + FixedDeduction.create(amount, category, deduct_day) + flash('固定扣款設定新增成功') + return redirect(url_for('fixed_deduction.index')) + +@fixed_deduction_bp.route('//delete', methods=['POST']) +def delete_fixed_deduction(deduction_id): + """刪除指定的每月固定扣款""" + FixedDeduction.delete(deduction_id) + flash('固定扣款已刪除') + return redirect(url_for('fixed_deduction.index')) diff --git a/app/routes/main.py b/app/routes/main.py new file mode 100644 index 00000000..a5eb0b9d --- /dev/null +++ b/app/routes/main.py @@ -0,0 +1,34 @@ +from flask import Blueprint, render_template +from app.models.transaction import Transaction +from app.models.fixed_deduction import FixedDeduction +from datetime import datetime + +main_bp = Blueprint('main', __name__) + +def process_fixed_deductions(): + """背景處理固定扣款邏輯,檢查所有設定,若符合本月且到期則自動扣款""" + today = datetime.now() + current_month = today.strftime('%Y-%m') + current_day = today.day + + deductions = FixedDeduction.get_all() + for d in deductions: + if d['last_processed_month'] != current_month and d['deduct_day'] <= current_day: + # 建立支出紀錄 + Transaction.create('EXPENSE', d['amount'], d['category'], today.strftime('%Y-%m-%d')) + # 更新已處理月份 + FixedDeduction.update_last_processed(d['id'], current_month) + +@main_bp.route('/', methods=['GET']) +def index(): + """ + 首頁路由 + """ + # 觸發固定扣款背景處理 + process_fixed_deductions() + + # 取得最新資料 + total_balance = Transaction.get_total_balance() + recent_transactions = Transaction.get_all()[:5] # 取得最近5筆 + + return render_template('main/index.html', total_balance=total_balance, transactions=recent_transactions) diff --git a/app/routes/recipe.py b/app/routes/recipe.py new file mode 100644 index 00000000..d446bcbc --- /dev/null +++ b/app/routes/recipe.py @@ -0,0 +1,56 @@ +from flask import Blueprint, request, render_template, redirect, url_for, abort + +recipe_bp = Blueprint('recipe', __name__) + +@recipe_bp.route('/') +def index(): + """ + 列出所有食譜。 + 若有 ?q= 參數則篩選標題與食材。 + """ + pass + +@recipe_bp.route('/recipes/new') +def new_recipe(): + """ + 顯示新增食譜表單頁面。 + """ + pass + +@recipe_bp.route('/recipes', methods=['POST']) +def create_recipe(): + """ + 處理新增食譜請求。 + 接收表單資料,寫入 DB,完成後導回首頁。 + """ + pass + +@recipe_bp.route('/recipes/') +def show_recipe(id): + """ + 顯示單一食譜的詳細資料。 + """ + pass + +@recipe_bp.route('/recipes//edit') +def edit_recipe(id): + """ + 顯示修改食譜的表單頁面,將原資料填入表單中。 + """ + pass + +@recipe_bp.route('/recipes//update', methods=['POST']) +def update_recipe(id): + """ + 處裡食譜修改請求並寫入。 + 更新後導回食譜明細頁。 + """ + pass + +@recipe_bp.route('/recipes//delete', methods=['POST']) +def delete_recipe(id): + """ + 刪除指定食譜。 + 刪除後導向回首頁。 + """ + pass diff --git a/app/routes/transaction.py b/app/routes/transaction.py new file mode 100644 index 00000000..a9ccec8b --- /dev/null +++ b/app/routes/transaction.py @@ -0,0 +1,54 @@ +from flask import Blueprint, request, redirect, render_template, flash, url_for +from app.models.transaction import Transaction + +transaction_bp = Blueprint('transaction', __name__, url_prefix='/transactions') + +@transaction_bp.route('/', methods=['GET']) +def index(): + """收支查詢清單路由""" + start_date = request.args.get('start_date') + end_date = request.args.get('end_date') + + if start_date and end_date: + transactions = Transaction.get_by_date_range(start_date, end_date) + else: + transactions = Transaction.get_all() + + return render_template('transactions/index.html', transactions=transactions, start_date=start_date, end_date=end_date) + +@transaction_bp.route('/new', methods=['GET']) +def new_transaction(): + """新增收支表單頁面""" + tx_type = request.args.get('type', 'EXPENSE') + return render_template('transactions/form.html', tx_type=tx_type) + +@transaction_bp.route('/', methods=['POST']) +def create_transaction(): + """儲存新的收支紀錄""" + tx_type = request.form.get('type') + amount_str = request.form.get('amount') + category = request.form.get('category') + transaction_date = request.form.get('transaction_date') + + if not amount_str or not category or not transaction_date or not tx_type: + flash('請填寫所有欄位') + return redirect(url_for('transaction.new_transaction', type=tx_type)) + + try: + amount = int(amount_str) + if amount <= 0: + raise ValueError() + except ValueError: + flash('金額必須為正整數') + return redirect(url_for('transaction.new_transaction', type=tx_type)) + + Transaction.create(tx_type, amount, category, transaction_date) + flash('收支紀錄新增成功') + return redirect(url_for('main.index')) + +@transaction_bp.route('//delete', methods=['POST']) +def delete_transaction(record_id): + """刪除特定收支紀錄""" + Transaction.delete(record_id) + flash('紀錄已刪除') + return redirect(url_for('transaction.index')) diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 00000000..d17a933c --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,132 @@ + + + + + + 個人記帳簿系統 + + + + + + + + +
+ {% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + {% endwith %} + + {% block content %}{% endblock %} +
+ + + + diff --git a/app/templates/fixed_deductions/form.html b/app/templates/fixed_deductions/form.html new file mode 100644 index 00000000..d86c62b5 --- /dev/null +++ b/app/templates/fixed_deductions/form.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+

記錄固定支出

+

設定後系統將會在每個月指定的時間自動將其納入總餘額的計算中。

+ +
+ +
+ +
+ $ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + 取消並返回清單 +
+
+
+
+
+{% endblock %} diff --git a/app/templates/fixed_deductions/index.html b/app/templates/fixed_deductions/index.html new file mode 100644 index 00000000..7ddfef85 --- /dev/null +++ b/app/templates/fixed_deductions/index.html @@ -0,0 +1,66 @@ +{% extends "base.html" %} + +{% block content %} +
+
+

每月固定扣款設定

+ + 新增自動扣款 + +
+ +
+ +
+

什麼是固定扣款?

+

系統會在您每個月登入時,檢查預設扣款日是否已到期;一旦到期將會自動為您建立對應的支出紀錄,讓您不必每個月重新記帳。

+
+
+ +
+ + + + + + + + + + + + {% for item in deductions %} + + + + + + + + {% else %} + + + + {% endfor %} + +
預期扣款日支出分類項目扣款金額上次自動執行月份操作
+ + 每月 {{ item.deduct_day }} 號 + + {{ item.category }}-${{ "{:,}".format(item.amount) }} + {% if item.last_processed_month %} + {{ item.last_processed_month }} + {% else %} + 尚未遇過扣款期 + {% endif %} + +
+ +
+
+ + 您目前沒有設定任何固定扣款項目。 +
+
+
+{% endblock %} diff --git a/app/templates/main/index.html b/app/templates/main/index.html new file mode 100644 index 00000000..a781bb33 --- /dev/null +++ b/app/templates/main/index.html @@ -0,0 +1,62 @@ +{% extends "base.html" %} + +{% block content %} +
+ +
+
+
My Total Balance
+

+ ${{ "{:,}".format(total_balance) }} +

+ +
+
+ + +
+
+
+

最新紀錄

+ 檢視全部 +
+ + {% if transactions %} +
+ + + {% for tx in transactions %} + + + + + + {% endfor %} + +
{{ tx.transaction_date[5:] }} + {% if tx.type == 'INCOME' %} + {{ tx.category }} + {% else %} + {{ tx.category }} + {% endif %} + + {% if tx.type == 'INCOME' %}+{% else %}-{% endif %}${{ "{:,}".format(tx.amount) }} +
+
+ {% else %} +
+ +

乾乾淨淨!開始記下你的第一筆帳吧

+
+ {% endif %} +
+
+
+{% endblock %} diff --git a/app/templates/transactions/form.html b/app/templates/transactions/form.html new file mode 100644 index 00000000..ee4c279b --- /dev/null +++ b/app/templates/transactions/form.html @@ -0,0 +1,52 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+

+ {% if tx_type == 'INCOME' %} + 記錄新收入 + {% else %} + 記錄新支出 + {% endif %} +

+ +
+ + + +
+ +
+ $ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + 取消並返回首頁 +
+
+
+
+
+ + +{% endblock %} diff --git a/app/templates/transactions/index.html b/app/templates/transactions/index.html new file mode 100644 index 00000000..7234a77f --- /dev/null +++ b/app/templates/transactions/index.html @@ -0,0 +1,69 @@ +{% extends "base.html" %} + +{% block content %} +
+
+

所有收支明細

+
+ +
+
+
+ + +
+
+ + +
+
+ + 清除 +
+
+
+ +
+ + + + + + + + + + + + {% for tx in transactions %} + + + + + + + + {% else %} + + + + {% endfor %} + +
日期類型分類項目產生金額操作
{{ tx.transaction_date }} + {% if tx.type == 'INCOME' %} + 收入 + {% else %} + 支出 + {% endif %} + {{ tx.category }} + {% if tx.type == 'INCOME' %}+{% else %}-{% endif %}${{ "{:,}".format(tx.amount) }} + +
+ +
+
+ 目前搜尋區段內無任何歷史紀錄。 +
+
+
+{% endblock %} diff --git a/database/schema.sql b/database/schema.sql new file mode 100644 index 00000000..8b393971 --- /dev/null +++ b/database/schema.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS transactions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + type TEXT NOT NULL, + amount INTEGER NOT NULL, + category TEXT NOT NULL, + transaction_date TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS fixed_deductions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + amount INTEGER NOT NULL, + category TEXT NOT NULL, + deduct_day INTEGER NOT NULL, + last_processed_month TEXT, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 00000000..93c9fb06 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,80 @@ +# 系統架構文件 (Architecture) - 個人記帳簿系統 + +## 1. 技術架構說明 + +根據 PRD 提出的需求與技術限制,我們選擇以下技術組合: + +- **後端框架:Python + Flask** + - **原因**:Flask 相較於 Django 更為輕量與彈性,非常適合用來開發這類功能集中且單純的個人記帳簿系統。 +- **模板引擎:Jinja2** + - **原因**:Jinja2 與 Flask 高度整合,能直接將後端資料無縫嵌入前端 HTML 中,有效降低開發複雜度。不需要建置前後端分離架構,所有頁面直接在伺服器渲染回傳。 +- **資料庫:SQLite** + - **原因**:這是一個「零配置」的關聯式資料庫,所有資料都儲存在單一檔案中,無需額外安裝或管理龐大的資料庫伺服器(如 MySQL 或 PostgreSQL),非常適合單機版或個人的網頁應用程式。 + +### Flask MVC 模式說明 +雖然 Flask 不像某些框架有嚴格的 MVC (Model-View-Controller) 規定,但我們依舊會採用類似概念來分類程式碼: +- **Model (資料模型)**:負責與 SQLite 資料庫溝通,定義「收入 (Income)」跟「支出 (Expense)」這些資料表記錄,並撰寫新增、修改、刪除的方法。 +- **View (視圖)**:負責呈現使用者見到的 UI 介面,在系統中就是由 **Jinja2** 組裝並渲染的 HTML 檔案。 +- **Controller (控制器)**:負責接收網頁請求與商業邏輯,在系統中就是 **Flask 的 Routes (路由)**。它會接收來自瀏覽器的操作(如:「新增一筆晚餐支出」),並指揮 Model 將結果儲存,最後選擇對應的 Jinja2 Template (View) 來顯示結果給使用者。 + +--- + +## 2. 專案資料夾結構 + +保持架構簡潔且易於擴充,我們規劃了如下資料夾結構: + +```text +web_app_development/ +├── app/ +│ ├── models/ ← (Model) 資料庫模型:定義資料表與資料存取邏輯 +│ ├── routes/ ← (Controller) Flask 路由:處理各個頁面的請求 +│ ├── templates/ ← (View) Jinja2 HTML 模板:畫面呈現 (如 Base 共用版型) +│ └── static/ ← CSS / JS 靜態資源:例如自定義的樣式表或圖表的 JS +├── instance/ +│ └── database.db ← SQLite 資料庫檔案:系統自動產生,存放實際記帳資料 +├── docs/ +│ ├── PRD.md ← 產品需求文件 +│ └── ARCHITECTURE.md ← 系統架構文件 (本檔案) +├── app.py ← 應用程式入口點:負責啟動 Flask 伺服器並註冊各路由 +└── requirements.txt ← 專案依賴套件表:記錄需要的模組清單 +``` + +--- + +## 3. 元件關係圖 + +以下是用以表示使用者在操作記帳系統時,各元件如何互相配合的流程關係: + +```mermaid +flowchart TD + Browser["瀏覽器 \n(Browser)"] + Router["Flask Route \n(Controller)"] + Model["資料模型 \n(Model)"] + DB[("SQLite 資料庫 \n(Database)")] + Template["Jinja2 Template \n(View)"] + + Browser -- "1. 請求 (如新增支出或看報表)" --> Router + Router -- "2. 查詢/寫入資料" --> Model + Model -- "3. 讀寫執行" --> DB + DB -- "4. 回傳資料" --> Model + Model -- "5. 將資料回傳" --> Router + Router -- "6. 傳入模板渲染" --> Template + Template -- "7. 渲染 HTML" --> Router + Router -- "8. 回傳頁面給使用者" --> Browser +``` + +--- + +## 4. 關鍵設計決策 + +1. **採用單體式與伺服器端渲染 (SSR)** + - *原因*:為了加速開發並符合不需前後端分離的限制,我們選擇將所有的畫面依賴 Jinja2 渲染完成。相較於架設一套 React/Vue 的架構,這種方式不必花時間設計複雜的 JSON API,能讓我們將重點集中在核心財務紀錄功能。 + +2. **目錄模組化設計 (`app/models` 與 `app/routes`)** + - *原因*:雖然這是一個 MVP 小型系統,但提早將路由與資料庫模型進行拆分,不僅能避免 `app.py` 變得過於龐大,如果未來要擴增新的圖表功能 (Nice to Have) 或資料匯出功能,也能保持優良的維護性。 + +3. **每月固定扣款的簡化實作** + - *原因*:PRD 中有提到「每月固定扣款」(Should Have) 功能。初期的 MVP 設計可由後端邏輯判斷是否跨月並「自動在背景新增記錄」,而非導入 Celery 等複雜的背景定期任務佇列,如此一來既能確保功能實作也維持了架構的輕量。 + +4. **採用關聯式資料庫架構** + - *原因*:財務記帳系統的特徵在於有明確且規律的資料屬性,例如:分類、金額、日期等。故比起 NoSQL,使用關聯式體系的 SQLite 能帶來更好的資料一致性限制,若之後系統要向外擴展 (Scale up),轉換至 PostgreSQL 的門檻也會較低。 diff --git a/docs/DB_DESIGN.md b/docs/DB_DESIGN.md new file mode 100644 index 00000000..b60bd725 --- /dev/null +++ b/docs/DB_DESIGN.md @@ -0,0 +1,93 @@ +# 資料庫設計文件 (DB Design) - 個人記帳簿系統 + +本文件根據產品需求與流程規劃,定義了系統後端使用 SQLite 的結構,包含實體關係圖、資料表詳細說明以及對應的 Python Model 設計。 + +## 1. ER 圖(實體關係圖) + +我們將收入與支出合併為 `transactions` 資料表,以類別(type)區分,方便計算總餘額與單一列表呈現。針對每月固定扣款項目則建立 `fixed_deductions`。 + +```mermaid +erDiagram + TRANSACTIONS { + INTEGER id PK + TEXT type "收入('INCOME') 或 支出('EXPENSE')" + INTEGER amount "金額" + TEXT category "分類,如 餐飲/交通" + TEXT transaction_date "發生日期 YYYY-MM-DD" + TEXT created_at "建立時間" + } + + FIXED_DEDUCTIONS { + INTEGER id PK + INTEGER amount "扣款金額" + TEXT category "分類,如 手機費/訂閱" + INTEGER deduct_day "每月固定扣款日 (1-31)" + TEXT last_processed_month "最後自動執行月份 YYYY-MM" + TEXT created_at "設定建立時間" + } + + %% 雖然兩者在邏輯上有關聯,但在資料儲存上,每次處理 fixed_deductions 都會產生一筆 transactions,屬於程式邏輯的寫入操作。 +``` + +--- + +## 2. 資料表詳細說明 + +### 2.1 `transactions` (收支紀錄表) +負責儲存所有使用者的日常收付款紀錄,是計算總餘額的核心。 + +| 欄位名稱 | 型別 | 必填 | 說明 | +| --- | --- | --- | --- | +| `id` | INTEGER | 是 | Primary Key 自動遞增 | +| `type` | TEXT | 是 | 區分此筆帳為 `'INCOME'` (收入) 或 `'EXPENSE'` (支出) | +| `amount` | INTEGER | 是 | 金額,限正整數 | +| `category` | TEXT | 是 | 使用者設定的收支分類,例如「餐飲」、「零用錢」 | +| `transaction_date`| TEXT | 是 | 付款或收款當天的日期,格式為 `YYYY-MM-DD` | +| `created_at` | TEXT | 否 | 資料庫寫入時間,由 `CURRENT_TIMESTAMP` 自動產生 | + +### 2.2 `fixed_deductions` (每月固定扣款設定表) +負責登錄使用者預先設定好的每月必扣款項,系統會在後端判斷該月份是否已經產生支出。 + +| 欄位名稱 | 型別 | 必填 | 說明 | +| --- | --- | --- | --- | +| `id` | INTEGER | 是 | Primary Key 自動遞增 | +| `amount` | INTEGER | 是 | 預設要扣款的金額 | +| `category` | TEXT | 是 | 預設的支出分類 | +| `deduct_day` | INTEGER | 是 | 預設扣款日,範圍 1 到 31 | +| `last_processed_month` | TEXT | 否 | 系統自動扣款後,會更新此欄為該筆已過帳的年月 (如 `2026-04`),避免重複扣款 | +| `created_at` | TEXT | 否 | 設定項的建立時間 | + +--- + +## 3. SQL 建表語法 + +請參考本專案下的 `database/schema.sql` 檔案。 + +```sql +CREATE TABLE IF NOT EXISTS transactions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + type TEXT NOT NULL, + amount INTEGER NOT NULL, + category TEXT NOT NULL, + transaction_date TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS fixed_deductions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + amount INTEGER NOT NULL, + category TEXT NOT NULL, + deduct_day INTEGER NOT NULL, + last_processed_month TEXT, + created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP +); +``` + +--- + +## 4. Python Model 程式碼 + +本專案使用最輕量的內建 `sqlite3` 提供資料存取層。相關連線與 CRUD 操作均已實作於 `app/models/` 目錄中: +- `app/models/db.py`:負責資料庫連線初始化 +- `app/models/transaction.py`:負責建立、查詢特定時間區段的所有收支紀錄 +- `app/models/fixed_deduction.py`:負責新增、與更新扣款執行狀態 diff --git a/docs/FLOWCHART.md b/docs/FLOWCHART.md new file mode 100644 index 00000000..12622a3b --- /dev/null +++ b/docs/FLOWCHART.md @@ -0,0 +1,71 @@ +# 流程圖設計文件 (Flowcharts) - 個人記帳簿系統 + +本文件基於 PRD 的需求與架構設計,展示系統的「使用者流程」與「系統序列流程」,以協助視覺化系統運作方式。 + +## 1. 使用者流程圖 (User Flow) + +描述使用者進入系統後,可能進行的各項操作路徑。 + +```mermaid +flowchart LR + Start([使用者開啟網頁]) --> Home[首頁 - 儀表板\n(顯示目前總餘額)] + + Home --> Action{要執行什麼操作?} + + Action -->|新增收支| AddSelect[選擇新增收入或支出] + AddSelect -->|輸入金額與分類| FillForm[填寫資料表單] + FillForm --> SubmitAdd[送出儲存] + SubmitAdd --> Home + + Action -->|查詢或篩選| Records[進入歷史收支紀錄頁面] + Records -->|選擇時間區間| Filter[套用日期或月份條件] + Filter --> ShowResult[顯示篩選後的收支結果列表] + ShowResult --> Home + + Action -->|管理固定扣款| Fixed[進入固定扣款設定頁] + Fixed -->|新增固定扣款| AddFixed[填寫每月固定扣款項目] + AddFixed --> Home +``` + +--- + +## 2. 系統序列圖 (System Sequence Diagram) + +以下以「使用者操作新增一筆支出」為例,展示前端瀏覽器、Flask 路由、Model 與 SQLite 資料庫之間的互動流程。 + +```mermaid +sequenceDiagram + actor User as 使用者 + participant Browser as 瀏覽器 (HTML 介面) + participant Flask as Flask Route (Controller) + participant Model as Expense Model + participant DB as SQLite 資料庫 + + User->>Browser: 點擊「新增支出」,填寫金額與分類 + User->>Browser: 點擊「送出」按鈕 + Browser->>Flask: POST /expense/add (傳送表單資料) + Flask->>Flask: 驗證接收到的表單格式與資料 + Flask->>Model: 呼叫 add_expense(amount, category, date) + Model->>DB: 執行 INSERT INTO expenses ... + DB-->>Model: 回傳寫入成功 + Model-->>Flask: 處理完成 + Flask-->>Browser: 重導向 (Redirect) 回首頁 + Browser-->>User: 顯示最新總餘額與該筆支出紀錄 +``` + +--- + +## 3. 功能清單對照表 + +整理未來需要實作的路由對應表,作為之後 API 設計或模板實作的參考依據。 + +| 功能名稱 | 說明 | URL 路徑 | HTTP 動作 | +| --- | --- | --- | --- | +| **首頁與總餘額** | 顯示當前可用餘額與最近幾筆收支紀錄,若有跨月狀況,載入時負責觸發自動建立固定扣款。 | `/` | `GET` | +| **新增收入頁面** | 渲染新增收入的表單畫面。 | `/income/add` | `GET` | +| **處理新增收入** | 處理表單送出的收入資料,寫入後重導至首頁。 | `/income/add` | `POST` | +| **新增支出頁面** | 渲染新增支出的表單畫面。 | `/expense/add` | `GET` | +| **處理新增支出** | 處理表單送出的支出資料,寫入後重導至首頁。 | `/expense/add` | `POST` | +| **收支查詢清單** | 顯示所有紀錄,支援讀取 URL query 參數(如 `?start_date=xxx`)來過濾特定區間。 | `/records` | `GET` | +| **固定扣款設定頁** | 顯示與管理每月的固定扣款項目。 | `/fixed-deduction` | `GET` | +| **新增固定扣款** | 處理表單送出的固定扣款資料。 | `/fixed-deduction` | `POST` | diff --git a/docs/PRD.md b/docs/PRD.md new file mode 100644 index 00000000..a8dd6bfb --- /dev/null +++ b/docs/PRD.md @@ -0,0 +1,70 @@ +# 產品需求文件 (PRD) - 個人記帳簿系統 + +## 1. 專案概述 + +### 背景與動機 +許多學生在日常生活中常面臨收支管理不善、月底吃土的問題。為了幫助學生建立良好的財務觀念,本系統旨在提供一個簡單易用的個人記帳本,記錄並追蹤日常收入與支出,讓用戶能夠清晰掌握資金動向與餘額狀況。 + +### 目標用戶 +- 學生(高中生、大學生) + +### 核心價值主張 +提供直覺且專注收支記錄的記帳系統,幫助學生輕鬆分類消費、設定固定扣款,並透過統計功能快速掌握目前的財務健康度。 + +--- + +## 2. 功能需求 + +### 主要功能與使用者故事 + +1. **收入分類** + - *使用者故事*:作為學生,我希望能夠新增並分類我的收入(如:零用錢、打工薪水、獎學金),以便我清楚知道錢從哪裡來。 + +2. **支出分類** + - *使用者故事*:作為學生,我希望能夠記錄支出並將其分類(如:餐飲、交通、娛樂、學習),以便我分析每個月或每週把錢花在什麼地方。 + +3. **每月固定扣款** + - *使用者故事*:作為學生,我希望系統能夠自動記錄或設定每月的固定扣款(如:手機費、定期的訂閱服務費),以便我不需要每個月重複手動輸入這筆必定發生的支出。 + +4. **總餘額統計** + - *使用者故事*:作為學生,我希望能在首頁即時看到目前的總餘額,以便我隨時了解自己目前可動用的剩餘資金。 + +5. **一段時間內的收入和支出查詢** + - *使用者故事*:作為學生,我希望能夠查詢特定時間段(如:本月、上週、特定日期區間)的收入與支出總計,以便我進行短期或長期的預算檢討與規劃。 + +--- + +## 3. 非功能需求 + +### 技術限制 +- 前端與後端:Flask + Jinja2 +- 資料庫:SQLite + +### 效能與安全考量 +- **效能考量**:系統需能在使用者輸入資料後,迅速更新餘額與報表。資料查詢(特別是一段時間的篩選)應具備良好反應速度。 +- **安全考量**:若未來發展至多用戶版本,需實作用戶身份驗證與授權機制,確保個別用戶的財務資料具備隱私性且不被他人存取。系統輸入應防範基本的 SQL Injection 與 XSS 攻擊。 + +--- + +## 4. MVP 範圍 (Minimum Viable Product) + +| 功能項目 | MVP 層級 | 說明 | +| --- | --- | --- | +| 收入/支出記錄與分類 | Must Have | 最基本的記帳功能,支援自定義分類 | +| 總餘額即時計算與顯示 | Must Have | 首頁的核心數據呈現 | +| 一段時間的收支查詢 | Must Have | 篩選特定區間並顯示總和 | +| 每月固定扣款設定 | Should Have | 方便自動扣款與減少重複操作,可在第二階段優化 | +| 收支圓餅圖或長條圖顯示 | Nice to Have | 視覺化報表,讓數據更直觀 | +| 匯出報表功能 (如 CSV) | Nice to Have | 供進階梳理需求使用 | + +--- + +## 5. 專案成員與分工 + +| 角色 | 姓名 | 負責項目 | +| --- | --- | --- | +| PM / SA | [林呈諺] | 需求分析、PRD 撰寫與更新 | +| UI/UX 設計師 | [林呈諺] | 介面規劃、使用者體驗設計 | +| 前端工程師 | [林呈諺] | Jinja2 模板實作、前端互動 | +| 後端工程師 | [林呈諺] | Flask 路由、SQLite Schema 設計與 API 實作 | +| 測試工程師 | [林呈諺] | 功能驗證與錯誤回報 | diff --git a/docs/ROUTES.md b/docs/ROUTES.md new file mode 100644 index 00000000..c7207e21 --- /dev/null +++ b/docs/ROUTES.md @@ -0,0 +1,68 @@ +# 路由與頁面設計文件 (Routes) - 個人記帳簿系統 + +基於 PRD、架構文件以及資料庫設計,本文件定義了 Flask 的所有端點路由,以供前端頁面進行資料綁定與操作。 + +## 1. 路由總覽表格 + +| 功能 | HTTP 方法 | URL 路徑 | 對應模板 | 說明 | +| --- | --- | --- | --- | --- | +| 首頁 (總餘額與最新紀錄) | GET | `/` | `templates/main/index.html` | 顯示目前總餘額,以及最近的幾筆收支紀錄。並隱性觸發自動扣款檢查。 | +| 收支查詢清單 | GET | `/transactions` | `templates/transactions/index.html`| 顯示所有紀錄,支援以日期過濾。 | +| 新增收支頁面 | GET | `/transactions/new` | `templates/transactions/form.html` | 顯示新增收入或支出的表單介面。 | +| 建立收支 | POST | `/transactions` | — | 接收使用者送出表單,寫入資料庫後重導至首頁。 | +| 刪除單筆收支 | POST | `/transactions//delete`| — | 刪除指定記錄,避免意外誤刪故採用 POST 實作,完畢後重導至列表頁。 | +| 固定扣款清單 | GET | `/fixed-deductions` | `templates/fixed_deductions/index.html`| 顯示與管理每月設定的固定支出。 | +| 新增固定扣款頁面| GET | `/fixed-deductions/new` | `templates/fixed_deductions/form.html` | 顯示建立新自動扣款的表單。 | +| 建立固定扣款 | POST | `/fixed-deductions` | — | 接收使用者送出表單,寫入設定並重導回清單。 | +| 刪除固定扣款 | POST | `/fixed-deductions//delete`| — | 取消該項目的後續每月自動扣款計算。 | + +--- + +## 2. 每個路由的詳細說明 + +### 首頁模組 +- `GET /` + - **輸入**:無。 + - **處理邏輯**:觸發 FixedDeduction 自動扣款檢查邏輯;調用 `Transaction.get_total_balance()` 與 `Transaction.get_all()` (限制前幾筆) 來獲取基礎儀表板資料。 + - **輸出**:渲染 `main/index.html`。 + - **錯誤處理**:如果資料庫裡沒任何記錄,無影響,正常回傳預設的 0 或空陣列。 + +### 收支實體 (Transaction) 模組 +- `GET /transactions` + - **輸入**:URL 查詢參數 `start_date`, `end_date` (可選)。 + - **處理邏輯**:判斷是否有參數傳入,決定呼叫 `get_all()` 還是 `get_by_date_range()`。 + - **輸出**:渲染 `transactions/index.html`。 +- `POST /transactions` + - **輸入**:表單參數 `type`, `amount`, `category`, `transaction_date`。 + - **處理邏輯**:驗證 `amount` 是否為正整數、`date` 是否合法後,存入 DB。 + - **輸出**:Redirect 至 `/` (快速查看結果) 或是 `/transactions`。 + - **錯誤處理**:如果必填欄位空白,存回 flash message,重導回表單頁。 + +### 固定扣款 (Fixed Deduction) 模組 +- `POST /fixed-deductions` + - **輸入**:表單參數 `amount`, `category`, `deduct_day`。 + - **處理邏輯**:驗證 `deduct_day` 是否落於 1-31,並存入資料庫。 + - **輸出**:Redirect 至 `/fixed-deductions`。 + - **錯誤處理**:日期超出月份合理範圍時 flash 錯誤提示。 + +--- + +## 3. Jinja2 模板清單 + +所有的網頁模板都會共用 `base.html` 提供的導覽列結構與樣式。 + +* **共用版型** + * `templates/base.html`:包含 HTML5 Skeleton、NavBar、全域共用 CSS。 +* **首頁** + * `templates/main/index.html`:儀表板 (Dashboard) 設計,繼承自 `base.html`。 +* **收支頁面** + * `templates/transactions/index.html`:含區間篩選器的歷史表格,繼承自 `base.html`。 + * `templates/transactions/form.html`:包含表單元素的新增頁面,繼承自 `base.html`。 +* **固定扣款頁面** + * `templates/fixed_deductions/index.html`:展示扣款設定清單,繼承自 `base.html`。 + * `templates/fixed_deductions/form.html`:固定扣款特定欄位的新增頁面,繼承自 `base.html`。 + +--- + +## 4. 路由骨架程式碼 +相關定義已實作於 `app/routes/` 下的 `main.py`, `transaction.py`, 與 `fixed_deduction.py` 之中。 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..f34604ee --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +flask +python-dotenv diff --git "a/\345\257\246\344\275\234\350\252\252\346\230\216.md" "b/\345\257\246\344\275\234\350\252\252\346\230\216.md" index 83b7a6b9..cc8c9399 100644 --- "a/\345\257\246\344\275\234\350\252\252\346\230\216.md" +++ "b/\345\257\246\344\275\234\350\252\252\346\230\216.md" @@ -195,8 +195,7 @@ commit 訊息:docs: add user flowchart **Step 4.4** — 請 AI 提交並推送: ``` -請幫我 commit 並推送目前的變更。 -commit 訊息:feat: add database schema and models +/commit ``` --- @@ -222,8 +221,7 @@ commit 訊息:feat: add database schema and models **Step 5.4** — 請 AI 提交並推送: ``` -請幫我 commit 並推送目前的變更。 -commit 訊息:feat: add route skeleton and template plan +/commit ``` ---