180 lines
6.0 KiB
Dart
180 lines
6.0 KiB
Dart
import 'package:sqflite/sqflite.dart';
|
|
import 'package:path/path.dart';
|
|
import '../models/category_model.dart';
|
|
import '../models/transaction_model.dart';
|
|
|
|
class DatabaseHelper {
|
|
static final DatabaseHelper instance = DatabaseHelper._init();
|
|
static Database? _database;
|
|
|
|
DatabaseHelper._init();
|
|
|
|
Future<Database> get database async {
|
|
if (_database != null) return _database!;
|
|
_database = await _initDB('expense_tracker.db');
|
|
return _database!;
|
|
}
|
|
|
|
Future<Database> _initDB(String filePath) async {
|
|
final dbPath = await getDatabasesPath();
|
|
final path = join(dbPath, filePath);
|
|
|
|
return await openDatabase(
|
|
path,
|
|
version: 2,
|
|
onCreate: _createDB,
|
|
onUpgrade: _upgradeDB,
|
|
);
|
|
}
|
|
|
|
Future _createDB(Database db, int version) async {
|
|
const idType = 'INTEGER PRIMARY KEY AUTOINCREMENT';
|
|
const textType = 'TEXT NOT NULL';
|
|
const realType = 'REAL NOT NULL';
|
|
const intType = 'INTEGER NOT NULL';
|
|
const intNullableType = 'INTEGER';
|
|
|
|
// Create Categories Table
|
|
await db.execute('''
|
|
CREATE TABLE categories (
|
|
id $idType,
|
|
name $textType,
|
|
type $textType,
|
|
icon $textType,
|
|
color $intType,
|
|
parentId $intNullableType
|
|
)
|
|
''');
|
|
|
|
// Create Transactions Table
|
|
await db.execute('''
|
|
CREATE TABLE transactions (
|
|
id TEXT PRIMARY KEY,
|
|
amount $realType,
|
|
type $textType,
|
|
categoryId $intType,
|
|
date $intType,
|
|
note TEXT,
|
|
createdAt $intType,
|
|
updatedAt $intType,
|
|
FOREIGN KEY (categoryId) REFERENCES categories (id)
|
|
)
|
|
''');
|
|
|
|
// Insert Default Categories
|
|
await _insertDefaultCategories(db);
|
|
}
|
|
|
|
Future _upgradeDB(Database db, int oldVersion, int newVersion) async {
|
|
if (oldVersion < 2) {
|
|
await db.execute('ALTER TABLE categories ADD COLUMN parentId INTEGER');
|
|
}
|
|
}
|
|
|
|
Future _insertDefaultCategories(Database db) async {
|
|
// Parent Categories
|
|
final foodId = await db.insert('categories', Category(name: '餐饮', type: 'expense', icon: 'restaurant', color: 0xFFFF5722).toMap());
|
|
final transportId = await db.insert('categories', Category(name: '交通', type: 'expense', icon: 'directions_bus', color: 0xFF2196F3).toMap());
|
|
final shoppingId = await db.insert('categories', Category(name: '购物', type: 'expense', icon: 'shopping_cart', color: 0xFFE91E63).toMap());
|
|
await db.insert('categories', Category(name: '娱乐', type: 'expense', icon: 'movie', color: 0xFF9C27B0).toMap());
|
|
await db.insert('categories', Category(name: '医疗', type: 'expense', icon: 'local_hospital', color: 0xFFF44336).toMap());
|
|
|
|
// Income
|
|
await db.insert('categories', Category(name: '工资', type: 'income', icon: 'attach_money', color: 0xFF4CAF50).toMap());
|
|
await db.insert('categories', Category(name: '投资', type: 'income', icon: 'trending_up', color: 0xFF009688).toMap());
|
|
|
|
// Sub Categories
|
|
await db.insert('categories', Category(name: '早餐', type: 'expense', icon: 'breakfast_dining', color: 0xFFFF5722, parentId: foodId).toMap());
|
|
await db.insert('categories', Category(name: '午餐', type: 'expense', icon: 'lunch_dining', color: 0xFFFF5722, parentId: foodId).toMap());
|
|
await db.insert('categories', Category(name: '晚餐', type: 'expense', icon: 'dinner_dining', color: 0xFFFF5722, parentId: foodId).toMap());
|
|
|
|
await db.insert('categories', Category(name: '地铁', type: 'expense', icon: 'subway', color: 0xFF2196F3, parentId: transportId).toMap());
|
|
await db.insert('categories', Category(name: '打车', type: 'expense', icon: 'local_taxi', color: 0xFF2196F3, parentId: transportId).toMap());
|
|
|
|
await db.insert('categories', Category(name: '衣服', type: 'expense', icon: 'checkroom', color: 0xFFE91E63, parentId: shoppingId).toMap());
|
|
await db.insert('categories', Category(name: '数码', type: 'expense', icon: 'devices', color: 0xFFE91E63, parentId: shoppingId).toMap());
|
|
}
|
|
|
|
// Transactions CRUD
|
|
Future<void> createTransaction(TransactionModel transaction) async {
|
|
final db = await instance.database;
|
|
await db.insert('transactions', transaction.toMap());
|
|
}
|
|
|
|
Future<TransactionModel> readTransaction(String id) async {
|
|
final db = await instance.database;
|
|
final maps = await db.query(
|
|
'transactions',
|
|
columns: null,
|
|
where: 'id = ?',
|
|
whereArgs: [id],
|
|
);
|
|
|
|
if (maps.isNotEmpty) {
|
|
return TransactionModel.fromMap(maps.first);
|
|
} else {
|
|
throw Exception('ID $id not found');
|
|
}
|
|
}
|
|
|
|
Future<List<TransactionModel>> readAllTransactions() async {
|
|
final db = await instance.database;
|
|
final orderBy = 'date DESC';
|
|
final result = await db.query('transactions', orderBy: orderBy);
|
|
|
|
return result.map((json) => TransactionModel.fromMap(json)).toList();
|
|
}
|
|
|
|
Future<int> updateTransaction(TransactionModel transaction) async {
|
|
final db = await instance.database;
|
|
return db.update(
|
|
'transactions',
|
|
transaction.toMap(),
|
|
where: 'id = ?',
|
|
whereArgs: [transaction.id],
|
|
);
|
|
}
|
|
|
|
Future<int> deleteTransaction(String id) async {
|
|
final db = await instance.database;
|
|
return await db.delete(
|
|
'transactions',
|
|
where: 'id = ?',
|
|
whereArgs: [id],
|
|
);
|
|
}
|
|
|
|
// Categories CRUD
|
|
Future<List<Category>> readAllCategories() async {
|
|
final db = await instance.database;
|
|
final result = await db.query('categories');
|
|
return result.map((json) => Category.fromMap(json)).toList();
|
|
}
|
|
|
|
Future<int> createCategory(Category category) async {
|
|
final db = await instance.database;
|
|
return await db.insert('categories', category.toMap());
|
|
}
|
|
|
|
Future<int> updateCategory(Category category) async {
|
|
final db = await instance.database;
|
|
return await db.update(
|
|
'categories',
|
|
category.toMap(),
|
|
where: 'id = ?',
|
|
whereArgs: [category.id],
|
|
);
|
|
}
|
|
|
|
Future<int> deleteCategory(int id) async {
|
|
final db = await instance.database;
|
|
// Check if it has children, if so, maybe update them or delete them?
|
|
// For simplicity, we won't cascade delete here but UI should warn.
|
|
return await db.delete(
|
|
'categories',
|
|
where: 'id = ?',
|
|
whereArgs: [id],
|
|
);
|
|
}
|
|
}
|