Add Task List Templates with CRUD controller

- TaskListTemplate and TaskListTemplateItem models
- TaskListTemplateController: list, get by ID, create with items, update (replace items), soft delete
- Migration for new tables
This commit is contained in:
Adam Moussa 2026-04-17 14:57:18 -04:00
parent 63f76e9b2c
commit 1f4bd12cd7
8 changed files with 2979 additions and 0 deletions

View file

@ -0,0 +1,146 @@
using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace Api.SeaHavenIndustries.Controllers
{
[Authorize]
[ApiController]
[Route("api/[controller]")]
[Route("api/tasklisttemplates")]
public class TaskListTemplateController : Controller
{
private readonly ApplicationDbContext _db;
public TaskListTemplateController(ApplicationDbContext db)
{
_db = db;
}
[HttpGet]
public async Task<IActionResult> GetAll()
{
var data = await _db.TaskListTemplates
.Where(t => t.IsActive)
.Include(t => t.Items.OrderBy(i => i.SortOrder))
.OrderBy(t => t.Name)
.Select(t => new
{
t.Id,
t.Name,
t.Description,
t.IsActive,
items = t.Items.OrderBy(i => i.SortOrder).Select(i => new
{
i.Id,
i.ItemText,
i.SortOrder
}).ToList()
})
.ToListAsync();
return Ok(data);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
var template = await _db.TaskListTemplates
.Include(t => t.Items.OrderBy(i => i.SortOrder))
.Where(t => t.Id == id)
.Select(t => new
{
t.Id,
t.Name,
t.Description,
t.IsActive,
items = t.Items.OrderBy(i => i.SortOrder).Select(i => new
{
i.Id,
i.ItemText,
i.SortOrder
}).ToList()
})
.FirstOrDefaultAsync();
if (template == null)
return NotFound(new Response { Status = "Error", Message = "Template not found" });
return Ok(template);
}
[HttpPost]
public async Task<IActionResult> Create([FromBody] TaskListTemplate_DTO model)
{
try
{
var template = new TaskListTemplate
{
Name = model.Name,
Description = model.Description,
IsActive = model.IsActive,
Items = model.Items.Select((item, idx) => new TaskListTemplateItem
{
ItemText = item.ItemText,
SortOrder = item.SortOrder > 0 ? item.SortOrder : idx
}).ToList()
};
_db.TaskListTemplates.Add(template);
await _db.SaveChangesAsync();
return Ok(new { message = "Template created", id = template.Id });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
[HttpPut("{id}")]
public async Task<IActionResult> Update(int id, [FromBody] TaskListTemplate_DTO model)
{
try
{
var template = await _db.TaskListTemplates
.Include(t => t.Items)
.FirstOrDefaultAsync(t => t.Id == id);
if (template == null)
return NotFound(new Response { Status = "Error", Message = "Template not found" });
template.Name = model.Name;
template.Description = model.Description;
template.IsActive = model.IsActive;
_db.TaskListTemplateItems.RemoveRange(template.Items);
template.Items = model.Items.Select((item, idx) => new TaskListTemplateItem
{
TaskListTemplateId = id,
ItemText = item.ItemText,
SortOrder = item.SortOrder > 0 ? item.SortOrder : idx
}).ToList();
await _db.SaveChangesAsync();
return Ok(new { message = "Template updated" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var template = await _db.TaskListTemplates.FindAsync(id);
if (template == null)
return NotFound(new Response { Status = "Error", Message = "Template not found" });
template.IsActive = false;
await _db.SaveChangesAsync();
return Ok(new { message = "Template deleted" });
}
}
}

View file

@ -0,0 +1,16 @@
namespace Api.SeaHavenIndustries.DTOs
{
public class TaskListTemplate_DTO
{
public string? Name { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; } = true;
public List<TaskListTemplateItem_DTO> Items { get; set; } = new();
}
public class TaskListTemplateItem_DTO
{
public string? ItemText { get; set; }
public int SortOrder { get; set; }
}
}

View file

@ -41,6 +41,8 @@ namespace Data.SeaHavenIndustries
public DbSet<DropdownOption> DropdownOptions { get; set; } public DbSet<DropdownOption> DropdownOptions { get; set; }
public DbSet<Vendor> Vendors { get; set; } public DbSet<Vendor> Vendors { get; set; }
public DbSet<Dispatch> Dispatches { get; set; } public DbSet<Dispatch> Dispatches { get; set; }
public DbSet<TaskListTemplate> TaskListTemplates { get; set; }
public DbSet<TaskListTemplateItem> TaskListTemplateItems { get; set; }
// New DbSets for added entities // New DbSets for added entities
public DbSet<Accounts> Accounts { get; set; } public DbSet<Accounts> Accounts { get; set; }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,80 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.SeaHavenIndustries.Migrations
{
/// <inheritdoc />
public partial class AddTaskListTemplates : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "TaskListTemplates",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
Description = table.Column<string>(type: "nvarchar(max)", nullable: true),
IsActive = table.Column<bool>(type: "bit", nullable: false),
IsDeleted = table.Column<bool>(type: "bit", nullable: true),
createdby = table.Column<string>(type: "nvarchar(max)", nullable: true),
DeleterUserId = table.Column<string>(type: "nvarchar(max)", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true),
CreatedDate = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierUserId = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_TaskListTemplates", x => x.Id);
});
migrationBuilder.CreateTable(
name: "TaskListTemplateItems",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
TaskListTemplateId = table.Column<int>(type: "int", nullable: false),
ItemText = table.Column<string>(type: "nvarchar(max)", nullable: true),
SortOrder = table.Column<int>(type: "int", nullable: false),
IsDeleted = table.Column<bool>(type: "bit", nullable: true),
createdby = table.Column<string>(type: "nvarchar(max)", nullable: true),
DeleterUserId = table.Column<string>(type: "nvarchar(max)", nullable: true),
DeletionTime = table.Column<DateTime>(type: "datetime2", nullable: true),
CreatedDate = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
LastModifierUserId = table.Column<int>(type: "int", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_TaskListTemplateItems", x => x.Id);
table.ForeignKey(
name: "FK_TaskListTemplateItems_TaskListTemplates_TaskListTemplateId",
column: x => x.TaskListTemplateId,
principalTable: "TaskListTemplates",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_TaskListTemplateItems_TaskListTemplateId",
table: "TaskListTemplateItems",
column: "TaskListTemplateId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TaskListTemplateItems");
migrationBuilder.DropTable(
name: "TaskListTemplates");
}
}
}

View file

@ -1386,6 +1386,94 @@ namespace Data.SeaHavenIndustries.Migrations
b.ToTable("Regions"); b.ToTable("Regions");
}); });
modelBuilder.Entity("Data.SeaHavenIndustries.TaskListTemplate", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime?>("CreatedDate")
.HasColumnType("datetime2");
b.Property<string>("DeleterUserId")
.HasColumnType("nvarchar(max)");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2");
b.Property<string>("Description")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsActive")
.HasColumnType("bit");
b.Property<bool?>("IsDeleted")
.HasColumnType("bit");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2");
b.Property<int?>("LastModifierUserId")
.HasColumnType("int");
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.Property<string>("createdby")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("TaskListTemplates");
});
modelBuilder.Entity("Data.SeaHavenIndustries.TaskListTemplateItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime?>("CreatedDate")
.HasColumnType("datetime2");
b.Property<string>("DeleterUserId")
.HasColumnType("nvarchar(max)");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2");
b.Property<bool?>("IsDeleted")
.HasColumnType("bit");
b.Property<string>("ItemText")
.HasColumnType("nvarchar(max)");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2");
b.Property<int?>("LastModifierUserId")
.HasColumnType("int");
b.Property<int>("SortOrder")
.HasColumnType("int");
b.Property<int>("TaskListTemplateId")
.HasColumnType("int");
b.Property<string>("createdby")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("TaskListTemplateId");
b.ToTable("TaskListTemplateItems");
});
modelBuilder.Entity("Data.SeaHavenIndustries.Template", b => modelBuilder.Entity("Data.SeaHavenIndustries.Template", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -2243,6 +2331,17 @@ namespace Data.SeaHavenIndustries.Migrations
b.Navigation("Quotes"); b.Navigation("Quotes");
}); });
modelBuilder.Entity("Data.SeaHavenIndustries.TaskListTemplateItem", b =>
{
b.HasOne("Data.SeaHavenIndustries.TaskListTemplate", "Template")
.WithMany("Items")
.HasForeignKey("TaskListTemplateId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Template");
});
modelBuilder.Entity("Data.SeaHavenIndustries.Template", b => modelBuilder.Entity("Data.SeaHavenIndustries.Template", b =>
{ {
b.HasOne("Data.SeaHavenIndustries.ApplicationUser", "AssignToUser") b.HasOne("Data.SeaHavenIndustries.ApplicationUser", "AssignToUser")
@ -2476,6 +2575,11 @@ namespace Data.SeaHavenIndustries.Migrations
b.Navigation("Employees"); b.Navigation("Employees");
}); });
modelBuilder.Entity("Data.SeaHavenIndustries.TaskListTemplate", b =>
{
b.Navigation("Items");
});
modelBuilder.Entity("Data.SeaHavenIndustries.Vendor", b => modelBuilder.Entity("Data.SeaHavenIndustries.Vendor", b =>
{ {
b.Navigation("Dispatches"); b.Navigation("Dispatches");

View file

@ -0,0 +1,10 @@
namespace Data.SeaHavenIndustries
{
public class TaskListTemplate : FullAuditEntity
{
public string? Name { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; } = true;
public ICollection<TaskListTemplateItem>? Items { get; set; }
}
}

View file

@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Data.SeaHavenIndustries
{
public class TaskListTemplateItem : FullAuditEntity
{
public int TaskListTemplateId { get; set; }
[ForeignKey(nameof(TaskListTemplateId))]
public virtual TaskListTemplate? Template { get; set; }
public string? ItemText { get; set; }
public int SortOrder { get; set; }
}
}