using System.ComponentModel; using trakker.Models; namespace trakker.Data { /// /// Provides data access methods for the entity. /// This class encapsulates database operations such as upsert, delete and ad-hoc /// SQL execution for projects. It inherits from which /// provides connection management. /// internal class ProjectData(string connectionString) : DataAccess(connectionString) { public BindingList Get(string? projectId = null) { var results = new BindingList(); string whereClause = "1 = 1"; if (projectId != null) { whereClause = "p.project_id = $project_id"; } string sql = $@" SELECT p.project_id, p.client_id, p.project_code, c.name AS client_name, p.name AS project_name, p.description, p.start_date, p.end_date, p.budget, p.status, l.display AS status_name, p.hourly_rate, IFNULL(a.amount, 0.0) AS actuals, p.notes, p.created_at, p.updated_at FROM projects p LEFT JOIN clients c ON p.client_id = c.client_id LEFT JOIN (SELECT project_id, SUM(hourly_rate * actual_hours) AS amount FROM tasks GROUP BY project_id) a ON p.project_id = a.project_id JOIN (SELECT value, display FROM lov WHERE source = 'project.status') l ON p.status = l.value WHERE {whereClause} ORDER BY p.start_date DESC, p.name ASC; ; "; using var conn = OpenConnection(); using var cmd = conn.CreateCommand(); cmd.CommandText = sql; if (projectId != null) { cmd.Parameters.AddWithValue("$project_id", projectId); } using var reader = cmd.ExecuteReader(); var _var1 = reader.GetOrdinal("project_id"); var _var2 = reader.GetOrdinal("client_id"); var _var3 = reader.GetOrdinal("project_code"); var _var4 = reader.GetOrdinal("client_name"); var _var5 = reader.GetOrdinal("project_name"); var _var6 = reader.GetOrdinal("description"); var _var7 = reader.GetOrdinal("start_date"); var _var8 = reader.GetOrdinal("end_date"); var _var9 = reader.GetOrdinal("budget"); var _var10 = reader.GetOrdinal("status"); var _var11 = reader.GetOrdinal("status_name"); var _var12 = reader.GetOrdinal("hourly_rate"); var _var13 = reader.GetOrdinal("notes"); var _var14 = reader.GetOrdinal("created_at"); var _var15 = reader.GetOrdinal("updated_at"); var _var16 = reader.GetOrdinal("actuals"); while (reader.Read()) { results.Add(new Project { ProjectId = reader.GetString(_var1), ClientId = reader.GetString(_var2), ProjectCode = reader.GetString(_var3), ClientName = reader.GetString(_var4), ProjectName = reader.GetString(_var5), Description = reader.GetString(_var6), StartDate = reader.IsDBNull(_var7) ? null : reader.GetDateTime(_var7), EndDate = reader.IsDBNull(_var8) ? null : reader.GetDateTime(_var8), Budget = reader.GetDecimal(_var9), Status = reader.GetString(_var10), StatusName = reader.GetString(_var11), HourlyRate = reader.GetDecimal(_var12), Notes = reader.GetString(_var13), CreatedAt = reader.GetDateTime(_var14), UpdatedAt = reader.GetDateTime(_var15), Actuals = reader.IsDBNull(_var16) ? null : reader.GetDecimal(_var16) }); } return results; } /// /// Inserts a new project record or updates an existing one (upsert) using /// the provided model. This method executes /// a single SQL statement inside a transaction and will commit on /// success or roll back on failure. /// /// The model to insert or update. Must not be null. /// /// The SQL statement uses an ON CONFLICT clause to perform the update when a /// matching project_id already exists. Parameter names correspond to the /// project model property names. /// public void Upsert(Project project) { const string sql = @" INSERT INTO projects ( project_id, client_id, name, description, start_date, end_date, budget, status, hourly_rate, notes ) VALUES ( $project_id, $client_id, $name, $description, $start_date, $end_date, $budget, $status, $hourly_rate, $notes ) ON CONFLICT (project_id) DO UPDATE SET client_id = excluded.client_id, name = excluded.name, description = excluded.description, start_date = excluded.start_date, end_date = excluded.end_date, budget = excluded.budget, status = excluded.status, hourly_rate = excluded.hourly_rate, notes = excluded.notes, updated_at = CURRENT_TIMESTAMP; "; using var conn = OpenConnection(); using var tx = conn.BeginTransaction(); try { using (var cmd = conn.CreateCommand()) { cmd.Transaction = tx; cmd.CommandText = sql; cmd.Parameters.AddWithValue("$project_id", project.ProjectId); cmd.Parameters.AddWithValue("$client_id", project.ClientId); cmd.Parameters.AddWithValue("$name", project.ProjectName); cmd.Parameters.AddWithValue("$description", project.Description); cmd.Parameters.AddWithValue("$start_date", project.StartDate); cmd.Parameters.AddWithValue("$end_date", project.EndDate); cmd.Parameters.AddWithValue("$budget", project.Budget); cmd.Parameters.AddWithValue("$status", project.Status); cmd.Parameters.AddWithValue("$hourly_rate", project.HourlyRate); cmd.Parameters.AddWithValue("$notes", project.Notes); cmd.ExecuteNonQuery(); } tx.Commit(); } catch { tx.Rollback(); throw; } } /// /// Deletes the project with the specified from the /// database. /// /// The identifier of the project to delete. /// An optional integer representing any scalar value returned by the /// command executed after deletion (if applicable). May be null. /// /// The method executes within a transaction. The current implementation attempts /// to read a scalar value after the delete; that value depends on surrounding /// database triggers or commands and may be null. /// public int? Delete(string projectId) { const string sql = @" DELETE FROM projects WHERE project_id = $project_id ; "; using var conn = OpenConnection(); using var tx = conn.BeginTransaction(); int? result = 0; try { using (var cmd = conn.CreateCommand()) { cmd.Transaction = tx; cmd.CommandText = sql; cmd.Parameters.AddWithValue("$project_id", projectId); cmd.ExecuteNonQuery(); } using var idCmd = conn.CreateCommand(); idCmd.Transaction = tx; result = (int?)idCmd.ExecuteScalar() ; tx.Commit(); } catch { tx.Rollback(); throw; } return result; } } }