mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 04:53:11 +00:00
Add comment types, audit logging, and WorkOrderAuditLog table
- Add CommentType field to Comments (customer, vendor, internal) - Add Status field to EditWorkorder_DTO - Create WorkOrderAuditLog model tracking field-level changes - Log all field changes on work order edit and status change - Include commentType and auditLog in GetWorkorderById response - Set CommentType=customer on synced comments from DynamoDB - Add BackfillCommentTypes endpoint for existing data
This commit is contained in:
parent
c69cef7fa9
commit
e1f57d3fb2
10 changed files with 2492 additions and 6 deletions
|
|
@ -176,6 +176,7 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
Commenttext = GetString(item, "text"),
|
||||
Commenter = GetString(item, "commenter"),
|
||||
RecordType = GetString(item, "record_type"),
|
||||
CommentType = "customer",
|
||||
CreatedDate = ParseDate(GetString(item, "created_at")) ?? DateTime.UtcNow,
|
||||
};
|
||||
|
||||
|
|
@ -196,6 +197,14 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
return Ok(new { synced, created, skipped });
|
||||
}
|
||||
|
||||
[HttpPost("BackfillCommentTypes")]
|
||||
public async Task<IActionResult> BackfillCommentTypes()
|
||||
{
|
||||
var updated = await _db.Database.ExecuteSqlRawAsync(
|
||||
"UPDATE Comments SET CommentType = 'customer' WHERE ExternalCommentId IS NOT NULL AND (CommentType IS NULL OR CommentType = '')");
|
||||
return Ok(new { updated });
|
||||
}
|
||||
|
||||
[HttpPost("All")]
|
||||
public async Task<IActionResult> SyncAll()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -184,6 +184,35 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
var workOrder = _db.workOrders.Where(w => w.Id == model.Id).FirstOrDefault();
|
||||
if (workOrder == null)
|
||||
return BadRequest(new Response { Status = "Error", Message = "Work order not found" });
|
||||
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var now = DateTime.UtcNow;
|
||||
void LogChange(string field, string? oldVal, string? newVal)
|
||||
{
|
||||
if (oldVal == newVal) return;
|
||||
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
|
||||
{
|
||||
WorkOrderId = workOrder.Id,
|
||||
UserId = userId,
|
||||
FieldName = field,
|
||||
OldValue = oldVal ?? "",
|
||||
NewValue = newVal ?? "",
|
||||
Action = "edit",
|
||||
CreatedAt = now
|
||||
});
|
||||
}
|
||||
|
||||
LogChange("WorkOrderNumber", workOrder.WorkerOrderNumber, model.WorkerOrderNumber);
|
||||
LogChange("Title", workOrder.WorkerOrderTitle, model.WorkerOrderTitle);
|
||||
LogChange("Description", workOrder.Description, model.Description);
|
||||
LogChange("TT", workOrder.TT, model.TT);
|
||||
LogChange("PO", workOrder.PO, model.PO);
|
||||
LogChange("AssignTo", workOrder.AssignTo, model.AssignTo);
|
||||
LogChange("DueDate", workOrder.DueDate?.ToString("yyyy-MM-dd"), model.DueDate?.ToString("yyyy-MM-dd"));
|
||||
LogChange("Priority", workOrder.Priority, model.Priority);
|
||||
LogChange("LocationId", workOrder.LocationId?.ToString(), model.LocationId?.ToString());
|
||||
LogChange("Status", workOrder.Status, model.Status);
|
||||
|
||||
workOrder.WorkerOrderNumber = model.WorkerOrderNumber;
|
||||
workOrder.WorkerOrderTitle = model.WorkerOrderTitle;
|
||||
workOrder.Description = model.Description;
|
||||
|
|
@ -192,10 +221,8 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
workOrder.AssignTo = model.AssignTo;
|
||||
workOrder.DueDate = model.DueDate;
|
||||
workOrder.Priority = model.Priority;
|
||||
//workOrder.CategoryId = model.CategoryId;
|
||||
workOrder.LocationId = model.LocationId;
|
||||
workOrder.Status = "Open";
|
||||
//workOrder.istemplate = false;
|
||||
workOrder.Status = model.Status;
|
||||
workOrder.BeforPhotoIssue = model.BeforPhotoIssue;
|
||||
workOrder.AfterPhotoIssue = model.AfterPhotoIssue;
|
||||
workOrder.SignOffName = model.SignOffName;
|
||||
|
|
@ -575,8 +602,20 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
s.SignOffName,
|
||||
s.SignOffAttachment,
|
||||
s.SignOffSignature,
|
||||
Comments = s.Comments.Select(c => new { c.Id, c.CreatedDate, c.Commenttext, c.Commenter, c.RecordType, c.Documents, userName = c.ApplicationUser != null ? c.ApplicationUser.FirstName : "" }),
|
||||
quotes = s.Quotes.Select(q => new { code = q.QuotId, q.Id })
|
||||
Comments = s.Comments.Select(c => new { c.Id, c.CreatedDate, c.Commenttext, c.Commenter, c.RecordType, c.CommentType, c.Documents, userName = c.ApplicationUser != null ? c.ApplicationUser.FirstName : "" }),
|
||||
quotes = s.Quotes.Select(q => new { code = q.QuotId, q.Id }),
|
||||
auditLog = _db.WorkOrderAuditLogs
|
||||
.Where(a => a.WorkOrderId == s.Id)
|
||||
.OrderByDescending(a => a.CreatedAt)
|
||||
.Select(a => new {
|
||||
a.Id,
|
||||
a.FieldName,
|
||||
a.OldValue,
|
||||
a.NewValue,
|
||||
a.Action,
|
||||
a.CreatedAt,
|
||||
userName = a.User != null ? a.User.FirstName + " " + a.User.LastName : ""
|
||||
}).ToList()
|
||||
}).FirstOrDefaultAsync();
|
||||
if (exist == null)
|
||||
{
|
||||
|
|
@ -720,6 +759,19 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
var data = await _db.workOrders.Where(w => w.Id == id).FirstOrDefaultAsync();
|
||||
if (data == null)
|
||||
return BadRequest(new Response { Status = "Error", Message = "Work order not found" });
|
||||
if (data.Status != status)
|
||||
{
|
||||
_db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
|
||||
{
|
||||
WorkOrderId = id,
|
||||
UserId = User.FindFirstValue(ClaimTypes.NameIdentifier),
|
||||
FieldName = "Status",
|
||||
OldValue = data.Status ?? "",
|
||||
NewValue = status,
|
||||
Action = "status_change",
|
||||
CreatedAt = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
data.Status = status;
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200", Data = data });
|
||||
|
|
@ -742,6 +794,7 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
comments.UserId = userId;
|
||||
comments.WorkerOrderId = model.WorkorderId;
|
||||
comments.Commenttext = model.Text;
|
||||
comments.CommentType = model.CommentType ?? "vendor";
|
||||
if (model.Document != null)
|
||||
{
|
||||
string fileName = ContentDispositionHeaderValue.Parse(model.Document.ContentDisposition).FileName.Trim('"');
|
||||
|
|
|
|||
|
|
@ -7,5 +7,6 @@
|
|||
public int WorkorderId { get; set; }
|
||||
public DateTime CreatedDate { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public string? CommentType { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,10 +44,10 @@ namespace Api.SeaHavenIndustries.DTOs
|
|||
public string? Description { get; set; }
|
||||
public string? PO { get; set; }
|
||||
public string? TT { get; set; }
|
||||
//public int? ContactId { get; set; }
|
||||
public string? AssignTo { get; set; }
|
||||
public DateTime? DueDate { get; set; }
|
||||
public string? Priority { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
//public int? CategoryId { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ namespace Data.SeaHavenIndustries
|
|||
public DbSet<WorkOrderContacts> WorkOrderContacts { get; set; }
|
||||
public DbSet<WorkOrderCategories> workOrderCategories { get; set; }
|
||||
public DbSet<WorkOrderAttachments> workOrderAttachments { get; set; }
|
||||
public DbSet<WorkOrderAuditLog> WorkOrderAuditLogs { get; set; }
|
||||
|
||||
// New DbSets for added entities
|
||||
public DbSet<Accounts> Accounts { get; set; }
|
||||
|
|
|
|||
2269
Data.SeaHavenIndustries/Migrations/20260416220656_AddCommentTypeAndAuditLog.Designer.cs
generated
Normal file
2269
Data.SeaHavenIndustries/Migrations/20260416220656_AddCommentTypeAndAuditLog.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Data.SeaHavenIndustries.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCommentTypeAndAuditLog : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "CommentType",
|
||||
table: "Comments",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "WorkOrderAuditLogs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
WorkOrderId = table.Column<int>(type: "int", nullable: false),
|
||||
UserId = table.Column<string>(type: "nvarchar(450)", nullable: true),
|
||||
FieldName = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
OldValue = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
NewValue = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
Action = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WorkOrderAuditLogs", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_WorkOrderAuditLogs_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_WorkOrderAuditLogs_workOrders_WorkOrderId",
|
||||
column: x => x.WorkOrderId,
|
||||
principalTable: "workOrders",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WorkOrderAuditLogs_UserId",
|
||||
table: "WorkOrderAuditLogs",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WorkOrderAuditLogs_WorkOrderId",
|
||||
table: "WorkOrderAuditLogs",
|
||||
column: "WorkOrderId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "WorkOrderAuditLogs");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CommentType",
|
||||
table: "Comments");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -376,6 +376,9 @@ namespace Data.SeaHavenIndustries.Migrations
|
|||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("CommentType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Commenter")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
|
|
@ -1537,6 +1540,44 @@ namespace Data.SeaHavenIndustries.Migrations
|
|||
b.ToTable("workOrderAttachments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.SeaHavenIndustries.WorkOrderAuditLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Action")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("FieldName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("NewValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("OldValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<int>("WorkOrderId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("WorkOrderId");
|
||||
|
||||
b.ToTable("WorkOrderAuditLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.SeaHavenIndustries.WorkOrderCategories", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
|
@ -2037,6 +2078,24 @@ namespace Data.SeaHavenIndustries.Migrations
|
|||
b.Navigation("WorkOrder");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.SeaHavenIndustries.WorkOrderAuditLog", b =>
|
||||
{
|
||||
b.HasOne("Data.SeaHavenIndustries.ApplicationUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("Data.SeaHavenIndustries.WorkOrder", "WorkOrder")
|
||||
.WithMany()
|
||||
.HasForeignKey("WorkOrderId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
|
||||
b.Navigation("WorkOrder");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.SeaHavenIndustries.WorkOrderCategories", b =>
|
||||
{
|
||||
b.HasOne("Data.SeaHavenIndustries.Category", "Category")
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ namespace Data.SeaHavenIndustries
|
|||
public string? Commenttext { get; set; }
|
||||
public string? Commenter { get; set; }
|
||||
public string? RecordType { get; set; }
|
||||
public string? CommentType { get; set; }
|
||||
public string? ExternalCommentId { get; set; }
|
||||
public string? Documents { get; set; }
|
||||
public string? UserId { get; set; }
|
||||
|
|
|
|||
20
Data.SeaHavenIndustries/Models/WorkOrderAuditLog.cs
Normal file
20
Data.SeaHavenIndustries/Models/WorkOrderAuditLog.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Data.SeaHavenIndustries
|
||||
{
|
||||
public class WorkOrderAuditLog
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int WorkOrderId { get; set; }
|
||||
[ForeignKey(nameof(WorkOrderId))]
|
||||
public virtual WorkOrder? WorkOrder { get; set; }
|
||||
public string? UserId { get; set; }
|
||||
[ForeignKey(nameof(UserId))]
|
||||
public virtual ApplicationUser? User { get; set; }
|
||||
public string? FieldName { get; set; }
|
||||
public string? OldValue { get; set; }
|
||||
public string? NewValue { get; set; }
|
||||
public string? Action { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue