using Microsoft.Data.Sqlite; namespace WheresMyMoney.Maui.Core; public class Repository { private static Repository? _instance; public static Repository Instance { get { if (_instance is not null) return _instance; string dbPath = Path.Combine(FileSystem.AppDataDirectory, "wheresmymoney.db"); _instance = new Repository(new SqliteConnection($"Data Source={dbPath};")); return _instance; } } private readonly SqliteConnection _connection; private Repository(SqliteConnection connection) { _connection = connection; _connection.Open(); CreateTables(); } ~Repository() { _connection.Close(); } public void CreateTables() { using var command = _connection.CreateCommand(); command.CommandText = """ CREATE TABLE IF NOT EXISTS planned_payment ( id INTEGER PRIMARY KEY AUTOINCREMENT, amount INTEGER NOT NULL, name TEXT NOT NULL, date_start TEXT NOT NULL, date_end TEXT NULL, is_subscription BOOLEAN NOT NULL, reoccurences INTEGER NULL, reoccurence_type INTEGER NULL CHECK(CASE reoccurences IS NOT NULL AND reoccurence_type IS NOT NULL WHEN TRUE THEN is_subscription ELSE NOT is_subscription END) ); CREATE TABLE IF NOT EXISTS balance ( id INTEGER PRIMARY KEY AUTOINCREMENT, amount INTEGER NOT NULL, date TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS settings ( id INTEGER PRIMARY KEY AUTOINCREMENT, payday TINYINT NOT NULL CHECK(payday BETWEEN 1 AND 31) ); """; command.ExecuteNonQuery(); } public void InsertPlannedPayment(decimal amount, string name, DateTime dateStart, DateTime? dateEnd, bool isSubscription, int? reoccurences, ReoccurenceType? reoccurenceType) { using var command = _connection.CreateCommand(); command.CommandText = """ INSERT INTO planned_payment (amount, name, date_start, date_end, is_subscription, reoccurences, reoccurence_type) VALUES (@amount, @name, @date_start, @date_end, @is_subscription, @reoccurences, @reoccurence_type); """; command.Parameters.AddWithValue("@amount", (int)(amount * 100)); command.Parameters.AddWithValue("@name", name); command.Parameters.AddWithValue("@date_start", dateStart); command.Parameters.AddWithValue("@date_end", dateEnd.HasValue ? dateEnd.Value : DBNull.Value); command.Parameters.AddWithValue("@is_subscription", isSubscription); command.Parameters.AddWithValue("@reoccurences", reoccurences is null ? DBNull.Value : reoccurences.Value); command.Parameters.AddWithValue("@reoccurence_type", reoccurenceType is null ? DBNull.Value : (int)reoccurenceType.Value); command.ExecuteNonQuery(); DataChanged(this, EventArgs.Empty); } public void InsertBalance(decimal amount, DateTime date) { using var command = _connection.CreateCommand(); command.CommandText = """ INSERT INTO balance (amount, date) VALUES (@amount, @date); """; command.Parameters.AddWithValue("@amount", (int)(amount * 100)); command.Parameters.AddWithValue("@date", date); command.ExecuteNonQuery(); DataChanged(this, EventArgs.Empty); } public decimal GetNewestBalance() { using var command = _connection.CreateCommand(); command.CommandText = @"SELECT amount FROM balance ORDER BY date DESC LIMIT 1;"; var result = command.ExecuteScalar(); return result != null ? Convert.ToDecimal(result) / 100m : 0m; } public List GetAllBalances() { var balances = new List(); using var command = _connection.CreateCommand(); command.CommandText = @"SELECT id, amount, date FROM balance ORDER BY date;"; using var reader = command.ExecuteReader(); while (reader.Read()) { balances.Add(new Balance( reader.GetInt32(0), reader.GetDecimal(1) / 100m, reader.GetDateTime(2) )); } return balances; } public byte GetPayday() { using var command = _connection.CreateCommand(); command.CommandText = "SELECT payday FROM settings LIMIT 1;"; var result = command.ExecuteScalar(); return result != null ? Convert.ToByte(result) : (byte)10; } public void ReplacePayday(byte newPayday) { using var command = _connection.CreateCommand(); command.CommandText = """ INSERT INTO settings (id, payday) VALUES (1, @payday) ON CONFLICT(id) DO UPDATE SET payday = excluded.payday; """; command.Parameters.AddWithValue("@payday", newPayday); command.ExecuteNonQuery(); DataChanged(this, EventArgs.Empty); } public List GetAllPlannedPayments() { var payments = new List(); using var command = _connection.CreateCommand(); command.CommandText = "SELECT id, amount, name, date_start, date_end, is_subscription, reoccurences, reoccurence_type FROM planned_payment ORDER BY DATETIME(date_start);"; using var reader = command.ExecuteReader(); while (reader.Read()) { payments.Add(new PlannedPayment( reader.GetInt32(0), reader.GetDecimal(1) / 100m, reader.GetString(2), reader.GetDateTime(3), reader.IsDBNull(4) ? null : reader.GetDateTime(4), reader.GetBoolean(5), reader.IsDBNull(6) ? null : reader.GetInt32(6), reader.IsDBNull(7) ? null : (ReoccurenceType)reader.GetInt32(7) )); } return payments; } public List GetFilteredPlannedPayments() { var filteredPayments = new List(); var now = DateTime.Now; var nearestPayday = GetNearestPayday(now); using var command = _connection.CreateCommand(); command.CommandText = """ SELECT id, amount, name, date_start, date_end, is_subscription, reoccurences, reoccurence_type FROM planned_payment WHERE ( (is_subscription = 0 AND DATETIME(date_start) >= DATETIME(@currentDate) AND DATETIME(date_start) <= DATETIME(@nearestPayday)) OR (is_subscription = 1 AND (date_end IS NULL OR DATETIME(date_end) >= DATETIME(@currentDate)) AND DATETIME(date_start) <= DATETIME(@nearestPayday)) ) ORDER BY DATETIME(date_start); """; command.Parameters.AddWithValue("@currentDate", now); command.Parameters.AddWithValue("@nearestPayday", nearestPayday); using var reader = command.ExecuteReader(); while (reader.Read()) { filteredPayments.Add(new PlannedPayment( reader.GetInt32(0), reader.GetDecimal(1) / 100m, reader.GetString(2), reader.GetDateTime(3), reader.IsDBNull(4) ? null : reader.GetDateTime(4), reader.GetBoolean(5), reader.IsDBNull(6) ? null : reader.GetInt32(6), reader.IsDBNull(7) ? null : (ReoccurenceType)reader.GetInt32(7) )); } return filteredPayments; } public DateTime GetNearestPayday(DateTime now) { var payday = GetPayday(); var nearestPayday = new DateOnly(now.Year, now.Month, payday > DateTime.DaysInMonth(now.Year, now.Month) ? DateTime.DaysInMonth(now.Year, now.Month) : payday) .ToDateTime(TimeOnly.MinValue); if (now.Day <= payday) return nearestPayday; var newDate = new DateOnly(now.Year, now.Month, DateTime.DaysInMonth(now.Year, now.Month)).AddDays(1); nearestPayday = new DateOnly(newDate.Year, newDate.Month, payday > DateTime.DaysInMonth(newDate.Year, newDate.Month) ? DateTime.DaysInMonth(newDate.Year, newDate.Month) : payday).ToDateTime(TimeOnly.MinValue); return nearestPayday; } public void RemovePlannedPayment(int id) { using var command = _connection.CreateCommand(); command.CommandText = """ DELETE FROM planned_payment WHERE id = @id; """; command.Parameters.AddWithValue("@id", id); command.ExecuteNonQuery(); DataChanged(this, EventArgs.Empty); } public event EventHandler DataChanged; }