552 lines
20 KiB
C#
552 lines
20 KiB
C#
using System.ComponentModel;
|
|
using trakker.Models;
|
|
|
|
namespace trakker.Data
|
|
{
|
|
/// <summary>
|
|
/// Provides data access methods for the <see cref="Models.PTask"/> entity.
|
|
/// This class encapsulates database operations such as upsert, delete and ad-hoc
|
|
/// SQL execution for tasks. It inherits from <see cref="DataAccess"/> which
|
|
/// provides connection management.
|
|
/// </summary>
|
|
internal class TaskData(string connectionString) : DataAccess(connectionString)
|
|
{
|
|
public BindingList<PTask> GetRecursive(string? id = null, PTask.RecursiveRoot root = PTask.RecursiveRoot.TASK_ID)
|
|
{
|
|
var results = new BindingList<PTask>();
|
|
|
|
string whereClause = "1 = 1";
|
|
switch(root)
|
|
{
|
|
case PTask.RecursiveRoot.TASK_ID:
|
|
if (id != null)
|
|
{
|
|
whereClause = "task_id = $id";
|
|
}
|
|
break;
|
|
case PTask.RecursiveRoot.PARENT_TASK_ID:
|
|
if (id != null)
|
|
{
|
|
whereClause = "parent_task_id = $id";
|
|
}
|
|
break;
|
|
}
|
|
|
|
//string sql = $@"
|
|
// SELECT
|
|
// task_id,
|
|
// project_id,
|
|
// title,
|
|
// description,
|
|
// status,
|
|
// status_name,
|
|
// priority,
|
|
// priority_name,
|
|
// due_date,
|
|
// estimated_hours,
|
|
// actual_hours,
|
|
// hourly_rate,
|
|
// amount,
|
|
// parent_task_id,
|
|
// created_at,
|
|
// updated_at
|
|
// FROM
|
|
// v$task_hierarchy
|
|
// WHERE
|
|
// {whereClause}
|
|
//";
|
|
|
|
string sql = $@"
|
|
WITH RECURSIVE TaskHierarchy AS (
|
|
-- Anchor: starting task(s)
|
|
SELECT
|
|
t0.task_id,
|
|
t0.project_id,
|
|
t0.title,
|
|
t0.description,
|
|
t0.status,
|
|
x0.display AS status_name,
|
|
t0.priority,
|
|
x1.display AS priority_name,
|
|
t0.due_date,
|
|
t0.estimated_hours,
|
|
t0.actual_hours,
|
|
t0.hourly_rate,
|
|
(t0.actual_hours * t0.hourly_rate) AS amount,
|
|
t0.parent_task_id,
|
|
t0.created_at,
|
|
t0.updated_at,
|
|
0 AS level,
|
|
t0.title AS path
|
|
FROM tasks t0
|
|
JOIN (SELECT value, display FROm lov WHERE source = 'task.status') x0 ON t0.status = x0.value
|
|
JOIN (SELECT value, display FROm lov WHERE source = 'task.priority') x1 ON t0.priority = x1.value
|
|
WHERE
|
|
{whereClause}
|
|
UNION ALL
|
|
|
|
-- Recursive part: get children
|
|
SELECT
|
|
t.task_id,
|
|
t.project_id,
|
|
t.title,
|
|
t.description,
|
|
t.status,
|
|
x0.display AS status_name,
|
|
t.priority,
|
|
x1.display AS priority_name,
|
|
t.due_date,
|
|
t.estimated_hours,
|
|
t.actual_hours,
|
|
t.hourly_rate,
|
|
(t.actual_hours * t.hourly_rate) AS amount,
|
|
t.parent_task_id,
|
|
t.created_at,
|
|
t.updated_at,
|
|
th.level + 1,
|
|
th.path || ' > ' || t.title
|
|
FROM tasks t
|
|
JOIN (SELECT value, display FROm lov WHERE source = 'task.status') x0 ON t.status = x0.value
|
|
JOIN (SELECT value, display FROm lov WHERE source = 'task.priority') x1 ON t.priority = x1.value
|
|
JOIN TaskHierarchy th ON t.parent_task_id = th.task_id
|
|
)
|
|
SELECT *
|
|
FROM TaskHierarchy
|
|
ORDER BY path
|
|
;
|
|
";
|
|
|
|
using var conn = OpenConnection();
|
|
using var cmd = conn.CreateCommand();
|
|
cmd.CommandText = sql;
|
|
|
|
if (id != null)
|
|
{
|
|
cmd.Parameters.AddWithValue("$id", id);
|
|
}
|
|
using var reader = cmd.ExecuteReader();
|
|
|
|
var _var1 = reader.GetOrdinal("task_id");
|
|
var _var2 = reader.GetOrdinal("project_id");
|
|
var _var3 = reader.GetOrdinal("title");
|
|
var _var4 = reader.GetOrdinal("description");
|
|
var _var5 = reader.GetOrdinal("status");
|
|
var _var6 = reader.GetOrdinal("status_name");
|
|
var _var7 = reader.GetOrdinal("priority");
|
|
var _var8 = reader.GetOrdinal("priority_name");
|
|
var _var9 = reader.GetOrdinal("due_date");
|
|
var _var10 = reader.GetOrdinal("estimated_hours");
|
|
var _var11 = reader.GetOrdinal("actual_hours");
|
|
var _var12 = reader.GetOrdinal("hourly_rate");
|
|
var _var13 = reader.GetOrdinal("amount");
|
|
var _var14 = reader.GetOrdinal("parent_task_id");
|
|
var _var15 = reader.GetOrdinal("created_at");
|
|
var _var16 = reader.GetOrdinal("updated_at");
|
|
|
|
while (reader.Read())
|
|
{
|
|
results.Add(new PTask
|
|
{
|
|
TaskId = reader.GetString(_var1),
|
|
ProjectId = reader.GetString(_var2),
|
|
Title = reader.GetString(_var3),
|
|
Description = reader.GetString(_var4),
|
|
Status = reader.GetString(_var5),
|
|
StatusName = reader.GetString(_var6),
|
|
Priority = reader.GetString(_var7),
|
|
PriorityName = reader.GetString(_var8),
|
|
DueDate = reader.IsDBNull(_var9) ? null : reader.GetDateTime(_var9),
|
|
EstimatedHours = reader.IsDBNull(_var10) ? null : reader.GetDouble(_var10),
|
|
ActualHours = reader.IsDBNull(_var11) ? null : reader.GetDouble(_var11),
|
|
HourlyRate = reader.IsDBNull(_var12) ? null : reader.GetDouble(_var12),
|
|
Amount = reader.IsDBNull(_var13) ? null : reader.GetDouble(_var13),
|
|
ParentTaskId = reader.IsDBNull(_var14) ? null : reader.GetString(_var14),
|
|
CreatedAt = reader.IsDBNull(_var15) ? null : reader.GetDateTime(_var15),
|
|
UpdatedAt = reader.IsDBNull(_var16) ? null : reader.GetDateTime(_var16),
|
|
});
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
public PTask Get(string? taskId = null)
|
|
{
|
|
var results = new List<PTask>();
|
|
|
|
string whereClause = "1 = 1";
|
|
if (taskId != null)
|
|
{
|
|
whereClause = "task_id = $task_id";
|
|
}
|
|
|
|
string sql = $@"
|
|
SELECT
|
|
task_id,
|
|
project_id,
|
|
title,
|
|
description,
|
|
status,
|
|
priority,
|
|
due_date,
|
|
estimated_hours,
|
|
actual_hours,
|
|
hourly_rate,
|
|
parent_task_id,
|
|
created_at,
|
|
updated_at
|
|
FROM tasks
|
|
WHERE
|
|
{whereClause}
|
|
ORDER BY
|
|
priority DESC,
|
|
due_date ASC,
|
|
created_at DESC
|
|
;
|
|
";
|
|
|
|
|
|
using var conn = OpenConnection();
|
|
using var cmd = conn.CreateCommand();
|
|
cmd.CommandText = sql;
|
|
|
|
if (taskId != null)
|
|
{
|
|
cmd.Parameters.AddWithValue("$task_id", taskId);
|
|
}
|
|
using var reader = cmd.ExecuteReader();
|
|
|
|
var _var1 = reader.GetOrdinal("task_id");
|
|
var _var2 = reader.GetOrdinal("project_id");
|
|
var _var3 = reader.GetOrdinal("title");
|
|
var _var4 = reader.GetOrdinal("description");
|
|
var _var5 = reader.GetOrdinal("status");
|
|
var _var6 = reader.GetOrdinal("priority");
|
|
var _var7 = reader.GetOrdinal("due_date");
|
|
var _var8 = reader.GetOrdinal("estimated_hours");
|
|
var _var9 = reader.GetOrdinal("actual_hours");
|
|
var _var10 = reader.GetOrdinal("hourly_rate");
|
|
var _var11 = reader.GetOrdinal("parent_task_id");
|
|
var _var12 = reader.GetOrdinal("created_at");
|
|
var _var13 = reader.GetOrdinal("updated_at");
|
|
|
|
while (reader.Read())
|
|
{
|
|
results.Add(new PTask
|
|
{
|
|
TaskId = reader.GetString(_var1),
|
|
ProjectId = reader.GetString(_var2),
|
|
Title = reader.GetString(_var3),
|
|
Description = reader.GetString(_var4),
|
|
Status = reader.GetString(_var5),
|
|
Priority = reader.GetString(_var6),
|
|
DueDate = reader.IsDBNull(_var7) ? null : reader.GetDateTime(_var7),
|
|
EstimatedHours = reader.IsDBNull(_var8) ? null : reader.GetDouble(_var8),
|
|
ActualHours = reader.IsDBNull(_var9) ? null : reader.GetDouble(_var9),
|
|
HourlyRate = reader.IsDBNull(_var10) ? null : reader.GetDouble(_var10),
|
|
ParentTaskId = reader.IsDBNull(_var11) ? null : reader.GetString(_var11),
|
|
CreatedAt = reader.IsDBNull(_var12) ? null : reader.GetDateTime(_var12),
|
|
UpdatedAt = reader.IsDBNull(_var13) ? null : reader.GetDateTime(_var13),
|
|
});
|
|
}
|
|
|
|
return results.FirstOrDefault() ?? new PTask();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a new task record or updates an existing one (upsert) using
|
|
/// the provided <paramref name="task"/> model. This method executes
|
|
/// a single SQL statement inside a transaction and will commit on
|
|
/// success or roll back on failure.
|
|
/// </summary>
|
|
/// <param name="task">The <see cref="PTask"/> 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>task_id</c> already exists. Parameter names correspond to the
|
|
/// task model property names.
|
|
/// </remarks>
|
|
public void Upsert(PTask task)
|
|
{
|
|
const string sql = @"
|
|
INSERT INTO tasks (
|
|
task_id,
|
|
project_id,
|
|
title,
|
|
description,
|
|
status,
|
|
priority,
|
|
due_date,
|
|
estimated_hours,
|
|
actual_hours,
|
|
hourly_rate,
|
|
parent_task_id
|
|
)
|
|
VALUES (
|
|
$task_id,
|
|
$project_id,
|
|
$title,
|
|
$description,
|
|
$status,
|
|
$priority,
|
|
$due_date,
|
|
$estimated_hours,
|
|
$actual_hours,
|
|
$hourly_rate,
|
|
$parent_task_id
|
|
)
|
|
ON CONFLICT(task_id) DO UPDATE SET
|
|
project_id = excluded.project_id,
|
|
title = excluded.title,
|
|
description = excluded.description,
|
|
status = excluded.status,
|
|
priority = excluded.priority,
|
|
due_date = excluded.due_date,
|
|
estimated_hours = excluded.estimated_hours,
|
|
actual_hours = excluded.actual_hours,
|
|
hourly_rate = excluded.hourly_rate,
|
|
parent_task_id = excluded.parent_task_id,
|
|
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("$task_id", task.TaskId);
|
|
cmd.Parameters.AddWithValue("$project_id", task.ProjectId);
|
|
cmd.Parameters.AddWithValue("$title", task.Title);
|
|
cmd.Parameters.AddWithValue("$description", task.Description);
|
|
cmd.Parameters.AddWithValue("$status", task.Status);
|
|
cmd.Parameters.AddWithValue("$priority", task.Priority);
|
|
cmd.Parameters.AddWithValue("$due_date", task.DueDate);
|
|
cmd.Parameters.AddWithValue("$estimated_hours", task.EstimatedHours);
|
|
cmd.Parameters.AddWithValue("$actual_hours", task.ActualHours);
|
|
cmd.Parameters.AddWithValue("$hourly_rate", task.HourlyRate);
|
|
cmd.Parameters.AddWithValue("$parent_task_id", task.ParentTaskId);
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
tx.Commit();
|
|
}
|
|
catch
|
|
{
|
|
tx.Rollback();
|
|
throw;
|
|
}
|
|
|
|
}
|
|
/// <summary>
|
|
/// Deletes the task with the specified <paramref name="taskId"/> from the
|
|
/// database.
|
|
/// </summary>
|
|
/// <param name="taskId">The identifier of the task 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 taskId)
|
|
{
|
|
const string sql = @"
|
|
DELETE FROM
|
|
tasks
|
|
WHERE
|
|
task_id = $task_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("$task_id", taskId);
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
using var idCmd = conn.CreateCommand();
|
|
idCmd.Transaction = tx;
|
|
result = (int?)idCmd.ExecuteScalar() ;
|
|
|
|
tx.Commit();
|
|
}
|
|
catch
|
|
{
|
|
tx.Rollback();
|
|
throw;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
public List<PTaskFS> GetFS(string projectId)
|
|
{
|
|
var results = new List<PTaskFS>();
|
|
|
|
string sql = $@"
|
|
SELECT
|
|
a.project_id AS guid,
|
|
a.name AS node,
|
|
'/' AS parent,
|
|
a.hourly_rate,
|
|
a.project_id
|
|
FROM
|
|
projects a
|
|
WHERE
|
|
a.project_id = $project_id
|
|
|
|
UNION
|
|
|
|
SELECT
|
|
b.task_id AS guid,
|
|
b.title AS node,
|
|
b.parent_task_id AS parent,
|
|
b.hourly_rate,
|
|
b.project_id
|
|
FROM
|
|
tasks b
|
|
WHERE
|
|
b.project_id = $project_id
|
|
;
|
|
";
|
|
|
|
using var conn = OpenConnection();
|
|
using var cmd = conn.CreateCommand();
|
|
cmd.CommandText = sql;
|
|
cmd.Parameters.AddWithValue("$project_id", projectId);
|
|
|
|
using var reader = cmd.ExecuteReader();
|
|
|
|
var _var1 = reader.GetOrdinal("guid");
|
|
var _var2 = reader.GetOrdinal("node");
|
|
var _var3 = reader.GetOrdinal("parent");
|
|
var _var4 = reader.GetOrdinal("hourly_rate");
|
|
var _var5 = reader.GetOrdinal("project_id");
|
|
|
|
while (reader.Read())
|
|
{
|
|
results.Add(new PTaskFS
|
|
{
|
|
GUID = reader.GetString(_var1),
|
|
Node = reader.GetString(_var2),
|
|
Parent = reader.GetString(_var3),
|
|
HourlyRate = reader.IsDBNull(_var4) ? (double?)null : reader.GetDouble(_var4),
|
|
ProjectId = reader.GetString(_var5)
|
|
});
|
|
}
|
|
|
|
return results;
|
|
}
|
|
public void SaveComment(PTaskComment taskComment)
|
|
{
|
|
const string sql = @"
|
|
INSERT INTO task_comments (
|
|
task_comment_id,
|
|
task_id,
|
|
comment
|
|
)
|
|
VALUES (
|
|
$task_comment_id,
|
|
$task_id,
|
|
$comment
|
|
)
|
|
ON CONFLICT(task_comment_id) DO UPDATE SET
|
|
task_id = excluded.task_id,
|
|
comment = excluded.comment
|
|
;
|
|
";
|
|
|
|
using var conn = OpenConnection();
|
|
using var tx = conn.BeginTransaction();
|
|
|
|
try
|
|
{
|
|
using (var cmd = conn.CreateCommand())
|
|
{
|
|
cmd.Transaction = tx;
|
|
cmd.CommandText = sql;
|
|
cmd.Parameters.AddWithValue("$task_comment_id", taskComment.TaskCommentId);
|
|
cmd.Parameters.AddWithValue("$task_id", taskComment.TaskId);
|
|
cmd.Parameters.AddWithValue("$comment", taskComment.Comment);
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
tx.Commit();
|
|
}
|
|
catch
|
|
{
|
|
tx.Rollback();
|
|
throw;
|
|
}
|
|
|
|
}
|
|
public List<PTaskComment> GetComments(string taskId)
|
|
{
|
|
var results = new List<PTaskComment>();
|
|
|
|
string whereClause = "1 = 1";
|
|
if (taskId != null)
|
|
{
|
|
whereClause = "task_id = $task_id";
|
|
}
|
|
|
|
string sql = $@"
|
|
SELECT
|
|
task_comment_id,
|
|
task_id,
|
|
comment,
|
|
created_at
|
|
FROM
|
|
task_comments
|
|
WHERE
|
|
{whereClause}
|
|
ORDER BY
|
|
created_at DESC
|
|
;
|
|
";
|
|
|
|
|
|
using var conn = OpenConnection();
|
|
using var cmd = conn.CreateCommand();
|
|
cmd.CommandText = sql;
|
|
|
|
if (taskId != null)
|
|
{
|
|
cmd.Parameters.AddWithValue("$task_id", taskId);
|
|
}
|
|
|
|
using var reader = cmd.ExecuteReader();
|
|
|
|
var _var1 = reader.GetOrdinal("task_comment_id");
|
|
var _var2 = reader.GetOrdinal("task_id");
|
|
var _var3 = reader.GetOrdinal("comment");
|
|
var _var4 = reader.GetOrdinal("created_at");
|
|
while (reader.Read())
|
|
{
|
|
results.Add(new PTaskComment
|
|
{
|
|
TaskCommentId = reader.GetString(_var1),
|
|
TaskId = reader.GetString(_var2),
|
|
Comment = reader.GetString(_var3),
|
|
CreatedAt = reader.IsDBNull(_var4) ? (DateTime?)null : reader.GetDateTime(_var4)
|
|
});
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
|
|
}
|
|
}
|