240 lines
9.3 KiB
C#
240 lines
9.3 KiB
C#
using System.ComponentModel;
|
|
using trakker.Models;
|
|
|
|
namespace trakker.Data
|
|
{
|
|
/// <summary>
|
|
/// Provides data access methods for the <see cref="Models.Project"/> entity.
|
|
/// This class encapsulates database operations such as upsert, delete and ad-hoc
|
|
/// SQL execution for projects. It inherits from <see cref="DataAccess"/> which
|
|
/// provides connection management.
|
|
/// </summary>
|
|
internal class ProjectData(string connectionString) : DataAccess(connectionString)
|
|
{
|
|
public BindingList<Project> Get(string? projectId = null)
|
|
{
|
|
var results = new BindingList<Project>();
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a new project record or updates an existing one (upsert) using
|
|
/// the provided <paramref name="project"/> model. This method executes
|
|
/// a single SQL statement inside a transaction and will commit on
|
|
/// success or roll back on failure.
|
|
/// </summary>
|
|
/// <param name="project">The <see cref="Client"/> model to insert or update. Must not be null.</param>
|
|
/// <remarks>
|
|
/// The SQL statement uses an ON CONFLICT clause to perform the update when a
|
|
/// matching <c>project_id</c> already exists. Parameter names correspond to the
|
|
/// project model property names.
|
|
/// </remarks>
|
|
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;
|
|
}
|
|
|
|
}
|
|
/// <summary>
|
|
/// Deletes the project with the specified <paramref name="projectId"/> from the
|
|
/// database.
|
|
/// </summary>
|
|
/// <param name="projectId">The identifier of the project to delete.</param>
|
|
/// <returns>An optional integer representing any scalar value returned by the
|
|
/// command executed after deletion (if applicable). May be null.</returns>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
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;
|
|
}
|
|
|
|
}
|
|
}
|