mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-07 16:19:07 +00:00
refactor(api): complete feature service boundaries
This commit is contained in:
parent
aa59399b78
commit
445cd68468
35 changed files with 2993 additions and 610 deletions
77
Api.SeaHavenIndustries.Tests/DispatchControllerTests.cs
Normal file
77
Api.SeaHavenIndustries.Tests/DispatchControllerTests.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
using Api.SeaHavenIndustries.Controllers;
|
||||||
|
using Data.SeaHavenIndustries;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Moq;
|
||||||
|
using SeaHaven.Services.DTOs;
|
||||||
|
using SeaHaven.Services.Interfaces;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Api.SeaHavenIndustries.Tests;
|
||||||
|
|
||||||
|
public class DispatchControllerTests
|
||||||
|
{
|
||||||
|
private static DispatchController NewController(Mock<IDispatchService> service) =>
|
||||||
|
new(service.Object);
|
||||||
|
|
||||||
|
private static object Prop(object source, string name) =>
|
||||||
|
source.GetType().GetProperty(name)!.GetValue(source)!;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task List_ReturnsDataResponseWithPaginationShape()
|
||||||
|
{
|
||||||
|
var service = new Mock<IDispatchService>();
|
||||||
|
service.Setup(s => s.GetDispatchListAsync("a", null, null, null, null, "dispatchedAt", "desc", 1, 25, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new DispatchListResultDTO
|
||||||
|
{
|
||||||
|
Total = 2,
|
||||||
|
Page = 1,
|
||||||
|
PageSize = 25,
|
||||||
|
Items = new List<DispatchListEntryDTO>
|
||||||
|
{
|
||||||
|
new() { Id = 1, DispatchNumber = "D1" },
|
||||||
|
new() { Id = 2, DispatchNumber = "D2" }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await NewController(service).List("a", null, null, null, null, "dispatchedAt", "desc", 1, 25, CancellationToken.None);
|
||||||
|
|
||||||
|
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = ok.Value.Should().BeOfType<DataResponse>().Subject;
|
||||||
|
response.Status.Should().Be("Success");
|
||||||
|
object data = response.Data!;
|
||||||
|
Prop(data, "total").Should().Be(2);
|
||||||
|
Prop(data, "page").Should().Be(1);
|
||||||
|
Prop(data, "pageSize").Should().Be(25);
|
||||||
|
((System.Collections.IEnumerable)Prop(data, "items")).Cast<object>().Count().Should().Be(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetById_WhenMissing_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
var service = new Mock<IDispatchService>();
|
||||||
|
service.Setup(s => s.GetDispatchDetailAsync(99, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync((DispatchDetailResultDTO?)null);
|
||||||
|
|
||||||
|
var result = await NewController(service).GetById(99, CancellationToken.None);
|
||||||
|
|
||||||
|
var notFound = result.Should().BeOfType<NotFoundObjectResult>().Subject;
|
||||||
|
notFound.Value.Should().BeOfType<Response>().Subject.Message.Should().Be("Dispatch not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetById_WhenFound_ReturnsDetailAsData()
|
||||||
|
{
|
||||||
|
var service = new Mock<IDispatchService>();
|
||||||
|
var detail = new DispatchDetailResultDTO { Id = 5, DispatchNumber = "D5", Status = "Pending" };
|
||||||
|
service.Setup(s => s.GetDispatchDetailAsync(5, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(detail);
|
||||||
|
|
||||||
|
var result = await NewController(service).GetById(5, CancellationToken.None);
|
||||||
|
|
||||||
|
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = ok.Value.Should().BeOfType<DataResponse>().Subject;
|
||||||
|
response.Status.Should().Be("Success");
|
||||||
|
((object?)response.Data).Should().BeSameAs(detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
117
Api.SeaHavenIndustries.Tests/DispatchServiceTests.cs
Normal file
117
Api.SeaHavenIndustries.Tests/DispatchServiceTests.cs
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using SeaHaven.DataServices.Dto;
|
||||||
|
using SeaHaven.DataServices.Interfaces;
|
||||||
|
using SeaHaven.Services.DTOs;
|
||||||
|
using SeaHaven.Services.Implementation;
|
||||||
|
using SeaHaven.Services.Interfaces;
|
||||||
|
using SeaHaven.Services.Validation;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Api.SeaHavenIndustries.Tests;
|
||||||
|
|
||||||
|
public class DispatchServiceTests
|
||||||
|
{
|
||||||
|
private static DispatchService NewService(Mock<IDispatchDataService> data) =>
|
||||||
|
new(data.Object, new CreateDispatchValidation(), new UpdateDispatchValidation());
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetDispatchListAsync_MapsDataReadModelToServiceDtoPreservingFields()
|
||||||
|
{
|
||||||
|
var data = new Mock<IDispatchDataService>();
|
||||||
|
var source = new DispatchListResult
|
||||||
|
{
|
||||||
|
Total = 1,
|
||||||
|
Page = 2,
|
||||||
|
PageSize = 25,
|
||||||
|
Items = new List<DispatchListItemDTO>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Id = 7,
|
||||||
|
DispatchNumber = "DISP-7",
|
||||||
|
PONumber = "PO-1",
|
||||||
|
NTEAmount = 200m,
|
||||||
|
Status = "Pending",
|
||||||
|
VendorId = 9,
|
||||||
|
VendorName = "Acme",
|
||||||
|
WorkOrderId = 3,
|
||||||
|
PrimaryWOInternalNumber = "WO-3",
|
||||||
|
PrimaryWOTitle = "Fix AC",
|
||||||
|
LocationName = "Site A",
|
||||||
|
WorkOrderCount = 2,
|
||||||
|
PendingUpliftCount = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
data.Setup(d => d.GetListAsync(It.IsAny<DispatchListQuery>(), It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(source);
|
||||||
|
|
||||||
|
var result = await NewService(data).GetDispatchListAsync("a", "Pending", 9, null, null, "ponumber", "asc", 2, 25, CancellationToken.None);
|
||||||
|
|
||||||
|
result.Should().BeOfType<DispatchListResultDTO>();
|
||||||
|
result.Total.Should().Be(1);
|
||||||
|
result.Page.Should().Be(2);
|
||||||
|
result.PageSize.Should().Be(25);
|
||||||
|
var row = result.Items.Single();
|
||||||
|
row.Id.Should().Be(7);
|
||||||
|
row.DispatchNumber.Should().Be("DISP-7");
|
||||||
|
row.PONumber.Should().Be("PO-1");
|
||||||
|
row.NTEAmount.Should().Be(200m);
|
||||||
|
row.Status.Should().Be("Pending");
|
||||||
|
row.VendorId.Should().Be(9);
|
||||||
|
row.VendorName.Should().Be("Acme");
|
||||||
|
row.WorkOrderId.Should().Be(3);
|
||||||
|
row.PrimaryWOInternalNumber.Should().Be("WO-3");
|
||||||
|
row.PrimaryWOTitle.Should().Be("Fix AC");
|
||||||
|
row.LocationName.Should().Be("Site A");
|
||||||
|
row.WorkOrderCount.Should().Be(2);
|
||||||
|
row.PendingUpliftCount.Should().Be(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetDispatchDetailAsync_MapsNestedCollectionsPreservingShape()
|
||||||
|
{
|
||||||
|
var data = new Mock<IDispatchDataService>();
|
||||||
|
var detail = new DispatchDetailDTO
|
||||||
|
{
|
||||||
|
Id = 42,
|
||||||
|
DispatchNumber = "DISP-42",
|
||||||
|
PONumber = "PO-9",
|
||||||
|
NTEAmount = 500m,
|
||||||
|
Status = "Dispatched",
|
||||||
|
Description = "desc",
|
||||||
|
WorkOrderId = 11,
|
||||||
|
EmailSent = true,
|
||||||
|
Vendor = new DispatchDetailVendorDTO { Id = 1, CompanyName = "VendorCo", Email = "v@x.com" },
|
||||||
|
PrimaryLocation = new DispatchPrimaryLocationDTO { Id = 5, Name = "Loc", City = "Austin" },
|
||||||
|
WorkOrders = new List<DispatchLinkedWorkOrderDTO> { new() { WorkOrderId = 11, Title = "T", Priority = "High" } },
|
||||||
|
Checklist = new List<DispatchChecklistDTO> { new() { Id = 1, ItemText = "Check", IsCompleted = true, SortOrder = 1 } },
|
||||||
|
Signoffs = new List<DispatchSignoffDTO> { new() { Id = 1, SignoffType = "Vendor", Name = "J" } },
|
||||||
|
Comments = new List<DispatchCommentDTO> { new() { Id = 1, Commenttext = "Hi", UserName = "Sara" } },
|
||||||
|
UpliftRequests = new List<DispatchUpliftRequestDTO> { new() { Id = 1, RequestedNTE = 600m, CurrentNTE = 500m, Delta = 100m, Status = "Pending" } },
|
||||||
|
AuditLog = new List<DispatchAuditLogDTO> { new() { Id = 1, Action = "Updated", FieldName = "DISP-42:Status" } }
|
||||||
|
};
|
||||||
|
data.Setup(d => d.GetDetailAsync(42, It.IsAny<CancellationToken>())).ReturnsAsync(detail);
|
||||||
|
data.Setup(d => d.GetDetailAsync(999, It.IsAny<CancellationToken>())).ReturnsAsync((DispatchDetailDTO?)null);
|
||||||
|
|
||||||
|
var service = NewService(data);
|
||||||
|
|
||||||
|
var found = await service.GetDispatchDetailAsync(42, CancellationToken.None);
|
||||||
|
var missing = await service.GetDispatchDetailAsync(999, CancellationToken.None);
|
||||||
|
|
||||||
|
missing.Should().BeNull();
|
||||||
|
found.Should().BeOfType<DispatchDetailResultDTO>();
|
||||||
|
found!.Id.Should().Be(42);
|
||||||
|
found.DispatchNumber.Should().Be("DISP-42");
|
||||||
|
found.EmailSent.Should().BeTrue();
|
||||||
|
found.Vendor!.CompanyName.Should().Be("VendorCo");
|
||||||
|
found.PrimaryLocation!.Name.Should().Be("Loc");
|
||||||
|
found.WorkOrders.Single().Title.Should().Be("T");
|
||||||
|
found.Checklist.Single().IsCompleted.Should().BeTrue();
|
||||||
|
found.Signoffs.Single().Name.Should().Be("J");
|
||||||
|
found.Comments.Single().UserName.Should().Be("Sara");
|
||||||
|
found.UpliftRequests.Single().Delta.Should().Be(100m);
|
||||||
|
found.AuditLog.Single().Action.Should().Be("Updated");
|
||||||
|
}
|
||||||
|
}
|
||||||
134
Api.SeaHavenIndustries.Tests/FollowUpControllerTests.cs
Normal file
134
Api.SeaHavenIndustries.Tests/FollowUpControllerTests.cs
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Api.SeaHavenIndustries.Controllers;
|
||||||
|
using Api.SeaHavenIndustries.DTOs;
|
||||||
|
using Data.SeaHavenIndustries;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Moq;
|
||||||
|
using SeaHaven.Services.DTOs;
|
||||||
|
using SeaHaven.Services.Interfaces;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Api.SeaHavenIndustries.Tests;
|
||||||
|
|
||||||
|
public class FollowUpControllerTests
|
||||||
|
{
|
||||||
|
private static FollowUpController NewController(Mock<IFollowUpService> service)
|
||||||
|
{
|
||||||
|
var controller = new FollowUpController(service.Object)
|
||||||
|
{
|
||||||
|
ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext
|
||||||
|
{
|
||||||
|
User = new ClaimsPrincipal(new ClaimsIdentity(new[]
|
||||||
|
{
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, "user-1")
|
||||||
|
}, "Test"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FollowUpController NewControllerNoUser(Mock<IFollowUpService> service) =>
|
||||||
|
new(service.Object)
|
||||||
|
{
|
||||||
|
ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext
|
||||||
|
{
|
||||||
|
User = new ClaimsPrincipal(new ClaimsIdentity())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static object Prop(object source, string name) =>
|
||||||
|
source.GetType().GetProperty(name)!.GetValue(source)!;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetFollowUps_ReturnsPaginationEnvelopeWithMappedRows()
|
||||||
|
{
|
||||||
|
var service = new Mock<IFollowUpService>();
|
||||||
|
service.Setup(s => s.GetFollowUpListAsync(1, 12, "", "Open", It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new PagedResult<FollowUpListRowDTO>
|
||||||
|
{
|
||||||
|
Items = new List<FollowUpListRowDTO> { new() { Id = 1, Reason = "R", CreatedByName = "Me" } },
|
||||||
|
TotalCount = 1,
|
||||||
|
Page = 1,
|
||||||
|
PageSize = 12
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await NewController(service).GetFollowUps();
|
||||||
|
|
||||||
|
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var envelope = ok.Value.Should().BeOfType<Pagination_DTO>().Subject;
|
||||||
|
envelope.PageSize.Should().Be(12);
|
||||||
|
envelope.TotalCount.Should().Be(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetById_WhenMissing_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
var service = new Mock<IFollowUpService>();
|
||||||
|
service.Setup(s => s.GetFollowUpDetailAsync(9, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync((FollowUpDetailDTO?)null);
|
||||||
|
|
||||||
|
var result = await NewController(service).GetById(9, CancellationToken.None);
|
||||||
|
|
||||||
|
result.Should().BeOfType<NotFoundResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Create_WhenNoUser_ReturnsUnauthorized()
|
||||||
|
{
|
||||||
|
var service = new Mock<IFollowUpService>();
|
||||||
|
var result = await NewControllerNoUser(service).Create(new FollowUp_DTO { Reason = "x" }, CancellationToken.None);
|
||||||
|
|
||||||
|
var unauthorized = result.Should().BeOfType<UnauthorizedObjectResult>().Subject;
|
||||||
|
unauthorized.Value.Should().BeOfType<Response>().Subject.Message.Should().Be("User not authenticated");
|
||||||
|
service.Verify(s => s.CreateOpenFollowUpAsync(It.IsAny<FollowUpCreateRequestDTO>(), It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Create_WhenAuthenticated_ReturnsCreatedWithId()
|
||||||
|
{
|
||||||
|
var service = new Mock<IFollowUpService>();
|
||||||
|
service.Setup(s => s.CreateOpenFollowUpAsync(It.IsAny<FollowUpCreateRequestDTO>(), "user-1", It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(42);
|
||||||
|
|
||||||
|
var result = await NewController(service).Create(new FollowUp_DTO { Reason = "x" }, CancellationToken.None);
|
||||||
|
|
||||||
|
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = ok.Value.Should().BeOfType<DataResponse>().Subject;
|
||||||
|
response.Status.Should().Be("200");
|
||||||
|
response.Message.Should().Be("Follow Up created");
|
||||||
|
((object?)response.Data).Should().Be(42);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Complete_WhenMissing_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
var service = new Mock<IFollowUpService>();
|
||||||
|
service.Setup(s => s.SetFollowUpStatusAsync(3, "Completed", It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(false);
|
||||||
|
|
||||||
|
var result = await NewController(service).Complete(3, CancellationToken.None);
|
||||||
|
|
||||||
|
result.Should().BeOfType<NotFoundResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Delete_WhenMissing_ReturnsNotFoundEnvelope()
|
||||||
|
{
|
||||||
|
var service = new Mock<IFollowUpService>();
|
||||||
|
service.Setup(s => s.RemoveFollowUpAsync(8, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(false);
|
||||||
|
|
||||||
|
var result = await NewController(service).Delete(8, CancellationToken.None);
|
||||||
|
|
||||||
|
var notFound = result.Should().BeOfType<NotFoundObjectResult>().Subject;
|
||||||
|
notFound.Value.Should().BeOfType<Response>().Subject.Message.Should().Be("Follow Up not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
162
Api.SeaHavenIndustries.Tests/FollowUpServiceTests.cs
Normal file
162
Api.SeaHavenIndustries.Tests/FollowUpServiceTests.cs
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
using Data.SeaHavenIndustries;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SeaHaven.DataServices.Implementation;
|
||||||
|
using SeaHaven.Services.DTOs;
|
||||||
|
using SeaHaven.Services.Implementation;
|
||||||
|
using SeaHaven.Services.Validation;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Api.SeaHavenIndustries.Tests;
|
||||||
|
|
||||||
|
public class FollowUpServiceTests
|
||||||
|
{
|
||||||
|
private static ApplicationDbContext NewContext()
|
||||||
|
{
|
||||||
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||||
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||||
|
.Options;
|
||||||
|
return new ApplicationDbContext(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FollowUpService NewService(ApplicationDbContext ctx) =>
|
||||||
|
new(new FollowUpDataService(ctx), new CreateFollowUpValidation(), new UpdateFollowUpValidation());
|
||||||
|
|
||||||
|
private static FollowUps SeedFollowUp(ApplicationDbContext ctx, string reason, string status = "Open")
|
||||||
|
{
|
||||||
|
var f = new FollowUps
|
||||||
|
{
|
||||||
|
Reason = reason,
|
||||||
|
ScheduleDate = new DateTime(2026, 8, 1),
|
||||||
|
ScheduleStartTime = new TimeSpan(9, 0, 0),
|
||||||
|
Status = status,
|
||||||
|
createdby = "user-1",
|
||||||
|
CreatedDate = DateTime.Now
|
||||||
|
};
|
||||||
|
ctx.FollowUps.Add(f);
|
||||||
|
ctx.SaveChanges();
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateOpenFollowUpAsync_ForcesOpenStatusAndPersistsFields()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
var service = NewService(ctx);
|
||||||
|
|
||||||
|
var id = await service.CreateOpenFollowUpAsync(new FollowUpCreateRequestDTO
|
||||||
|
{
|
||||||
|
Reason = "Call back",
|
||||||
|
ScheduleDate = new DateTime(2026, 9, 1),
|
||||||
|
ScheduleStartTime = new TimeSpan(10, 30, 0),
|
||||||
|
EmployeeId = 5,
|
||||||
|
WorkOrderId = 7,
|
||||||
|
AccountId = 9,
|
||||||
|
LocationId = 11
|
||||||
|
}, "user-9", CancellationToken.None);
|
||||||
|
|
||||||
|
var entity = ctx.FollowUps.Single();
|
||||||
|
entity.Id.Should().Be(id);
|
||||||
|
entity.Status.Should().Be("Open");
|
||||||
|
entity.Reason.Should().Be("Call back");
|
||||||
|
entity.createdby.Should().Be("user-9");
|
||||||
|
entity.EmployeeId.Should().Be(5);
|
||||||
|
entity.WorkOrderId.Should().Be(7);
|
||||||
|
entity.CreatedDate.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetFollowUpListAsync_DefaultsToOpenAndMapsShape()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
SeedFollowUp(ctx, "Open one", "Open");
|
||||||
|
SeedFollowUp(ctx, "Done one", "Completed");
|
||||||
|
|
||||||
|
var page = await NewService(ctx).GetFollowUpListAsync(1, 12, "", null, CancellationToken.None);
|
||||||
|
|
||||||
|
page.Items.Should().HaveCount(1);
|
||||||
|
page.Items.Single().Reason.Should().Be("Open one");
|
||||||
|
page.Items.Single().Status.Should().Be("Open");
|
||||||
|
page.PageSize.Should().Be(12);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetFollowUpDetailAsync_MapsRelatedNamesAndResolvesCreator()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
var employee = new Employee { FirstName = "Sara", LastName = "Lee" };
|
||||||
|
ctx.Employees.Add(employee);
|
||||||
|
ctx.SaveChanges();
|
||||||
|
|
||||||
|
var account = new Accounts { Name = "Acme" };
|
||||||
|
ctx.Accounts.Add(account);
|
||||||
|
ctx.SaveChanges();
|
||||||
|
|
||||||
|
var user = new ApplicationUser { Id = "user-1", FirstName = "Creator", LastName = "One", UserName = "cone" };
|
||||||
|
var fu = new FollowUps
|
||||||
|
{
|
||||||
|
Reason = "Detail",
|
||||||
|
ScheduleDate = new DateTime(2026, 8, 2),
|
||||||
|
ScheduleStartTime = new TimeSpan(8, 0, 0),
|
||||||
|
Status = "Open",
|
||||||
|
EmployeeId = employee.Id,
|
||||||
|
AccountId = account.Id,
|
||||||
|
createdby = "user-1",
|
||||||
|
CreatedDate = DateTime.Now
|
||||||
|
};
|
||||||
|
ctx.Users.Add(user);
|
||||||
|
ctx.FollowUps.Add(fu);
|
||||||
|
ctx.SaveChanges();
|
||||||
|
|
||||||
|
var detail = await NewService(ctx).GetFollowUpDetailAsync(fu.Id, CancellationToken.None);
|
||||||
|
|
||||||
|
detail.Should().NotBeNull();
|
||||||
|
detail!.AssignedTo.Should().Be("Sara Lee");
|
||||||
|
detail.AccountName.Should().Be("Acme");
|
||||||
|
detail.CreatedById.Should().Be("user-1");
|
||||||
|
detail.CreatedByName.Should().Be("Creator One");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateFollowUpFieldsAsync_UpdatesAndThrowsWhenMissing()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
var existing = SeedFollowUp(ctx, "Old reason");
|
||||||
|
|
||||||
|
await NewService(ctx).UpdateFollowUpFieldsAsync(existing.Id, new FollowUpUpdateRequestDTO
|
||||||
|
{
|
||||||
|
Id = existing.Id,
|
||||||
|
Reason = "New reason",
|
||||||
|
ScheduleDate = new DateTime(2026, 10, 1),
|
||||||
|
ScheduleStartTime = new TimeSpan(11, 0, 0)
|
||||||
|
}, CancellationToken.None);
|
||||||
|
|
||||||
|
var row = ctx.FollowUps.Single();
|
||||||
|
row.Reason.Should().Be("New reason");
|
||||||
|
|
||||||
|
var act = () => NewService(ctx).UpdateFollowUpFieldsAsync(existing.Id + 999, new FollowUpUpdateRequestDTO { Id = existing.Id + 999 }, CancellationToken.None);
|
||||||
|
await act.Should().ThrowAsync<KeyNotFoundException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SetFollowUpStatusAsync_TogglesStatusAndReturnsFalseWhenMissing()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
var existing = SeedFollowUp(ctx, "To complete");
|
||||||
|
|
||||||
|
(await NewService(ctx).SetFollowUpStatusAsync(existing.Id, "Completed", CancellationToken.None)).Should().BeTrue();
|
||||||
|
ctx.FollowUps.Single().Status.Should().Be("Completed");
|
||||||
|
(await NewService(ctx).SetFollowUpStatusAsync(existing.Id + 999, "Completed", CancellationToken.None)).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RemoveFollowUpAsync_RemovesAndReturnsFalseWhenMissing()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
var existing = SeedFollowUp(ctx, "Gone");
|
||||||
|
|
||||||
|
(await NewService(ctx).RemoveFollowUpAsync(existing.Id, CancellationToken.None)).Should().BeTrue();
|
||||||
|
(await NewService(ctx).RemoveFollowUpAsync(existing.Id, CancellationToken.None)).Should().BeFalse();
|
||||||
|
ctx.FollowUps.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
104
Api.SeaHavenIndustries.Tests/LocationControllerTests.cs
Normal file
104
Api.SeaHavenIndustries.Tests/LocationControllerTests.cs
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
using Api.SeaHavenIndustries.Controllers;
|
||||||
|
using Api.SeaHavenIndustries.DTOs;
|
||||||
|
using Data.SeaHavenIndustries;
|
||||||
|
using FluentAssertions;
|
||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Moq;
|
||||||
|
using SeaHaven.Services.DTOs;
|
||||||
|
using SeaHaven.Services.Interfaces;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Api.SeaHavenIndustries.Tests;
|
||||||
|
|
||||||
|
public class LocationControllerTests
|
||||||
|
{
|
||||||
|
private static LocationController NewController(Mock<ILocationService> service) =>
|
||||||
|
new(service.Object);
|
||||||
|
|
||||||
|
private static object Prop(object source, string name) =>
|
||||||
|
source.GetType().GetProperty(name)!.GetValue(source)!;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetLocationList_ReturnsPaginationEnvelopeWithServiceData()
|
||||||
|
{
|
||||||
|
var service = new Mock<ILocationService>();
|
||||||
|
service.Setup(s => s.GetLocationListPagedAsync(1, 10, "a", It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new PagedResult<LocationDTO>
|
||||||
|
{
|
||||||
|
Items = new List<LocationDTO> { new() { Id = 1, Name = "Site" } },
|
||||||
|
TotalCount = 1,
|
||||||
|
Page = 1,
|
||||||
|
PageSize = 10
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await NewController(service).GetLocationList("a", 1, 10, CancellationToken.None);
|
||||||
|
|
||||||
|
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var envelope = ok.Value.Should().BeOfType<Pagination_DTO>().Subject;
|
||||||
|
envelope.PageNumber.Should().Be(1);
|
||||||
|
envelope.PageSize.Should().Be(10);
|
||||||
|
envelope.TotalCount.Should().Be(1);
|
||||||
|
envelope.TotalPages.Should().Be(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetLocationById_WhenMissing_ReturnsNotFoundAndDoesNotLeakEntity()
|
||||||
|
{
|
||||||
|
var service = new Mock<ILocationService>();
|
||||||
|
service.Setup(s => s.GetLocationDetailAsync(5, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync((LocationDTO?)null);
|
||||||
|
|
||||||
|
var result = await NewController(service).GetLocationById(5, CancellationToken.None);
|
||||||
|
|
||||||
|
var notFound = result.Should().BeOfType<NotFoundObjectResult>().Subject;
|
||||||
|
var response = notFound.Value.Should().BeOfType<Response>().Subject;
|
||||||
|
response.Message.Should().Be("Location not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetLocationById_WhenFound_MapsServiceDtoToAliasedShape()
|
||||||
|
{
|
||||||
|
var service = new Mock<ILocationService>();
|
||||||
|
service.Setup(s => s.GetLocationDetailAsync(7, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new LocationDTO { Id = 7, Name = "N", Address1 = "A", Zip = "Z", PhoneNumber = "P", Email = "E", Status = "Active" });
|
||||||
|
|
||||||
|
var result = await NewController(service).GetLocationById(7, CancellationToken.None);
|
||||||
|
|
||||||
|
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var body = ok.Value!;
|
||||||
|
Prop(body, "Id").Should().Be(7);
|
||||||
|
Prop(body, "Name").Should().Be("N");
|
||||||
|
Prop(body, "Address").Should().Be("A");
|
||||||
|
Prop(body, "ZipCode").Should().Be("Z");
|
||||||
|
Prop(body, "Phone").Should().Be("P");
|
||||||
|
Prop(body, "ContactEmail").Should().Be("E");
|
||||||
|
Prop(body, "Status").Should().Be("Active");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddLocation_WhenServiceSucceeds_ReturnsCreatedResponse()
|
||||||
|
{
|
||||||
|
var service = new Mock<ILocationService>();
|
||||||
|
var result = await NewController(service).AddLocation(new Location_DTO { Name = "X" }, CancellationToken.None);
|
||||||
|
|
||||||
|
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = ok.Value.Should().BeOfType<DataResponse>().Subject;
|
||||||
|
response.Status.Should().Be("200");
|
||||||
|
response.Message.Should().Be("Location Created Successfully");
|
||||||
|
service.Verify(s => s.CreateLocationFromRequestAsync(It.IsAny<LocationCreateRequestDTO>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EditLocation_WhenMissing_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
var service = new Mock<ILocationService>();
|
||||||
|
service.Setup(s => s.UpdateLocationFromRequestAsync(99, It.IsAny<LocationUpdateRequestDTO>(), It.IsAny<CancellationToken>()))
|
||||||
|
.ThrowsAsync(new KeyNotFoundException());
|
||||||
|
|
||||||
|
var result = await NewController(service).EditLocation(99, new EditLocation_DTO { Name = "X" }, CancellationToken.None);
|
||||||
|
|
||||||
|
var notFound = result.Should().BeOfType<NotFoundObjectResult>().Subject;
|
||||||
|
notFound.Value.Should().BeOfType<Response>().Subject.Message.Should().Be("Location not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
133
Api.SeaHavenIndustries.Tests/LocationServiceTests.cs
Normal file
133
Api.SeaHavenIndustries.Tests/LocationServiceTests.cs
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
using Data.SeaHavenIndustries;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SeaHaven.DataServices.Implementation;
|
||||||
|
using SeaHaven.Services.DTOs;
|
||||||
|
using SeaHaven.Services.Implementation;
|
||||||
|
using SeaHaven.Services.Validation;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Api.SeaHavenIndustries.Tests;
|
||||||
|
|
||||||
|
public class LocationServiceTests
|
||||||
|
{
|
||||||
|
private static ApplicationDbContext NewContext()
|
||||||
|
{
|
||||||
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||||
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||||
|
.Options;
|
||||||
|
return new ApplicationDbContext(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LocationService NewService(ApplicationDbContext ctx) =>
|
||||||
|
new(new LocationDataService(ctx), new CreateLocationValidation(), new UpdateLocationValidation());
|
||||||
|
|
||||||
|
private static Locations SeedLocation(ApplicationDbContext ctx, string name, string? city = null, string? status = "Active")
|
||||||
|
{
|
||||||
|
var loc = new Locations { Name = name, City = city, Status = status, CreatedDate = DateTime.Now };
|
||||||
|
ctx.Locations.Add(loc);
|
||||||
|
ctx.SaveChanges();
|
||||||
|
return loc;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateLocationFromRequestAsync_PersistsMappedFields()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
var service = NewService(ctx);
|
||||||
|
|
||||||
|
await service.CreateLocationFromRequestAsync(new LocationCreateRequestDTO
|
||||||
|
{
|
||||||
|
Name = "Warehouse",
|
||||||
|
Title = "Main WH",
|
||||||
|
Address = "1 Depot Rd",
|
||||||
|
City = "Austin",
|
||||||
|
State = "TX",
|
||||||
|
ZipCode = "73301",
|
||||||
|
Phone = "555-1000",
|
||||||
|
ContactEmail = "wh@example.com",
|
||||||
|
Status = "Active"
|
||||||
|
}, CancellationToken.None);
|
||||||
|
|
||||||
|
var entity = ctx.Locations.Single();
|
||||||
|
entity.Name.Should().Be("Warehouse");
|
||||||
|
entity.Title.Should().Be("Main WH");
|
||||||
|
entity.Address1.Should().Be("1 Depot Rd");
|
||||||
|
entity.City.Should().Be("Austin");
|
||||||
|
entity.State.Should().Be("TX");
|
||||||
|
entity.Zip.Should().Be("73301");
|
||||||
|
entity.PhoneNumber.Should().Be("555-1000");
|
||||||
|
entity.Email.Should().Be("wh@example.com");
|
||||||
|
entity.Status.Should().Be("Active");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetLocationListPagedAsync_FiltersBySearchAndMapsShape()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
SeedLocation(ctx, "Alpha Site", "Austin");
|
||||||
|
SeedLocation(ctx, "Beta Site", "Dallas");
|
||||||
|
SeedLocation(ctx, "Gamma Yard", "Austin");
|
||||||
|
|
||||||
|
var page = await NewService(ctx).GetLocationListPagedAsync(1, 10, "Austin", CancellationToken.None);
|
||||||
|
|
||||||
|
page.Items.Should().HaveCount(2);
|
||||||
|
page.TotalCount.Should().Be(2);
|
||||||
|
page.Items.All(l => l.City == "Austin").Should().BeTrue();
|
||||||
|
page.Items.Select(l => l.Name).Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetLocationDetailAsync_ReturnsMappedDtoOrNull()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
var existing = SeedLocation(ctx, "Depot", "Houston");
|
||||||
|
|
||||||
|
var service = NewService(ctx);
|
||||||
|
var found = await service.GetLocationDetailAsync(existing.Id, CancellationToken.None);
|
||||||
|
var missing = await service.GetLocationDetailAsync(existing.Id + 999, CancellationToken.None);
|
||||||
|
|
||||||
|
missing.Should().BeNull();
|
||||||
|
found.Should().NotBeNull();
|
||||||
|
found!.Name.Should().Be("Depot");
|
||||||
|
found.City.Should().Be("Houston");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateLocationFromRequestAsync_UpdatesFieldsAndThrowsWhenMissing()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
var existing = SeedLocation(ctx, "Old", "Round Rock");
|
||||||
|
|
||||||
|
await NewService(ctx).UpdateLocationFromRequestAsync(existing.Id, new LocationUpdateRequestDTO
|
||||||
|
{
|
||||||
|
Name = "New",
|
||||||
|
Address = "9 New St",
|
||||||
|
City = "Plano",
|
||||||
|
Status = "Inactive"
|
||||||
|
}, CancellationToken.None);
|
||||||
|
|
||||||
|
var row = ctx.Locations.Single();
|
||||||
|
row.Name.Should().Be("New");
|
||||||
|
row.Address1.Should().Be("9 New St");
|
||||||
|
row.City.Should().Be("Plano");
|
||||||
|
row.Status.Should().Be("Inactive");
|
||||||
|
|
||||||
|
var act = () => NewService(ctx).UpdateLocationFromRequestAsync(existing.Id + 999, new LocationUpdateRequestDTO { Name = "X" }, CancellationToken.None);
|
||||||
|
await act.Should().ThrowAsync<KeyNotFoundException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteLocationByIdAsync_RemovesAndReturnsFalseWhenMissing()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
var existing = SeedLocation(ctx, "Gone", "Cedar Park");
|
||||||
|
|
||||||
|
var removed = await NewService(ctx).DeleteLocationByIdAsync(existing.Id, CancellationToken.None);
|
||||||
|
var again = await NewService(ctx).DeleteLocationByIdAsync(existing.Id, CancellationToken.None);
|
||||||
|
|
||||||
|
removed.Should().BeTrue();
|
||||||
|
again.Should().BeFalse();
|
||||||
|
ctx.Locations.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -54,4 +54,26 @@ public class ServiceRegistrationTests
|
||||||
provider.GetRequiredService<IDashboardDataService>().Should().BeOfType<DashboardDataService>();
|
provider.GetRequiredService<IDashboardDataService>().Should().BeOfType<DashboardDataService>();
|
||||||
provider.GetRequiredService<IDropdownOptionsDataService>().Should().BeOfType<DropdownOptionsDataService>();
|
provider.GetRequiredService<IDropdownOptionsDataService>().Should().BeOfType<DropdownOptionsDataService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ConventionDi_RegistersFollowUpLocationDispatchSettingsServices()
|
||||||
|
{
|
||||||
|
using var provider = BuildProvider();
|
||||||
|
|
||||||
|
provider.GetRequiredService<IFollowUpService>().Should().BeOfType<FollowUpService>();
|
||||||
|
provider.GetRequiredService<ILocationService>().Should().BeOfType<LocationService>();
|
||||||
|
provider.GetRequiredService<IDispatchService>().Should().BeOfType<DispatchService>();
|
||||||
|
provider.GetRequiredService<ISettingsService>().Should().BeOfType<SettingsService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ConventionDi_RegistersFollowUpLocationDispatchSettingsDataServices()
|
||||||
|
{
|
||||||
|
using var provider = BuildProvider();
|
||||||
|
|
||||||
|
provider.GetRequiredService<IFollowUpDataService>().Should().BeOfType<FollowUpDataService>();
|
||||||
|
provider.GetRequiredService<ILocationDataService>().Should().BeOfType<LocationDataService>();
|
||||||
|
provider.GetRequiredService<IDispatchDataService>().Should().BeOfType<DispatchDataService>();
|
||||||
|
provider.GetRequiredService<ISettingsDataService>().Should().BeOfType<SettingsDataService>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
119
Api.SeaHavenIndustries.Tests/SettingsControllerTests.cs
Normal file
119
Api.SeaHavenIndustries.Tests/SettingsControllerTests.cs
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
using Api.SeaHavenIndustries.Controllers;
|
||||||
|
using Api.SeaHavenIndustries.DTOs;
|
||||||
|
using Api.SeaHavenIndustries.Helper.ViewModel;
|
||||||
|
using Data.SeaHavenIndustries;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Moq;
|
||||||
|
using SeaHaven.Services.DTOs;
|
||||||
|
using SeaHaven.Services.Interfaces;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Api.SeaHavenIndustries.Tests;
|
||||||
|
|
||||||
|
public class SettingsControllerTests
|
||||||
|
{
|
||||||
|
private static SettingsController NewController(Mock<ISettingsService> service) =>
|
||||||
|
new(service.Object);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetCategoryList_ReturnsPaginationEnvelopeWithServiceItems()
|
||||||
|
{
|
||||||
|
var service = new Mock<ISettingsService>();
|
||||||
|
service.Setup(s => s.GetCategoryListAsync(1, 10, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync((new List<SettingsCategoryDTO> { new() { Id = 1, Name = "A" } }, 1));
|
||||||
|
|
||||||
|
var result = await NewController(service).GetCategoryList(1, 10, CancellationToken.None);
|
||||||
|
|
||||||
|
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var envelope = ok.Value.Should().BeOfType<Pagination_DTO>().Subject;
|
||||||
|
envelope.TotalCount.Should().Be(1);
|
||||||
|
envelope.TotalPages.Should().Be(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetTemplates_ReturnsTypedServiceDtosInEnvelope()
|
||||||
|
{
|
||||||
|
var service = new Mock<ISettingsService>();
|
||||||
|
service.Setup(s => s.GetTemplatesAsync(1, 10, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync((new List<SettingsTemplateDTO> { new() { Id = 1, WorkerOrderTitle = "T" } }, 1));
|
||||||
|
|
||||||
|
var result = await NewController(service).GetTemplates(1, 10, CancellationToken.None);
|
||||||
|
|
||||||
|
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var envelope = ok.Value.Should().BeOfType<Pagination_DTO>().Subject;
|
||||||
|
envelope.TotalCount.Should().Be(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddCategory_WhenServiceSucceeds_ReturnsSaved()
|
||||||
|
{
|
||||||
|
var service = new Mock<ISettingsService>();
|
||||||
|
var result = await NewController(service).AddCategory(new Category_DTO { Name = "X" }, CancellationToken.None);
|
||||||
|
|
||||||
|
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = ok.Value.Should().BeOfType<DataResponse>().Subject;
|
||||||
|
response.Status.Should().Be("200");
|
||||||
|
response.Message.Should().Be("Successfully Saved Data");
|
||||||
|
service.Verify(s => s.AddCategoryAsync("X", It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EditCategory_WhenMissing_ReturnsBadRequest()
|
||||||
|
{
|
||||||
|
var service = new Mock<ISettingsService>();
|
||||||
|
service.Setup(s => s.UpdateCategoryAsync(9, "X", It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(false);
|
||||||
|
|
||||||
|
var result = await NewController(service).EditCategory(new EditCategory_DTO { Id = 9, Name = "X" }, CancellationToken.None);
|
||||||
|
|
||||||
|
var bad = result.Should().BeOfType<BadRequestObjectResult>().Subject;
|
||||||
|
bad.Value.Should().BeOfType<Response>().Subject.Message.Should().Be("Category not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteCategory_WhenMissing_ReturnsIdNotMatched()
|
||||||
|
{
|
||||||
|
var service = new Mock<ISettingsService>();
|
||||||
|
service.Setup(s => s.DeleteCategoryAsync(9, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(false);
|
||||||
|
|
||||||
|
var result = await NewController(service).DeleteCategory(9, CancellationToken.None);
|
||||||
|
|
||||||
|
var bad = result.Should().BeOfType<BadRequestObjectResult>().Subject;
|
||||||
|
bad.Value.Should().BeOfType<Response>().Subject.Message.Should().Be("ID not matched!");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetUsers_MapsServiceUsersToUserRoleViewModel()
|
||||||
|
{
|
||||||
|
var service = new Mock<ISettingsService>();
|
||||||
|
service.Setup(s => s.GetUsersAsync("", 1, 10, It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync((new List<SettingsUserDTO>
|
||||||
|
{
|
||||||
|
new() { Id = "u1", UserName = "Sara", Email = "s@x.com", RoleName = "User", Status = "Active", Date = "2026-01-01" }
|
||||||
|
}, 1));
|
||||||
|
|
||||||
|
var result = await NewController(service).GetUsers("", 1, 10, CancellationToken.None);
|
||||||
|
|
||||||
|
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var envelope = ok.Value.Should().BeOfType<Pagination_DTO>().Subject;
|
||||||
|
object data = envelope.Data!;
|
||||||
|
var rows = ((System.Collections.IEnumerable)data).Cast<UserRoleViewModel>().ToList();
|
||||||
|
rows.Should().ContainSingle(u => u.UserName == "Sara" && u.RoleName == "User" && u.Email == "s@x.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetRoles_MapsServiceRolesToRolesDto()
|
||||||
|
{
|
||||||
|
var service = new Mock<ISettingsService>();
|
||||||
|
service.Setup(s => s.GetRolesAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new List<RoleDTO> { new() { Name = "Admin", Description = "Full Access" } });
|
||||||
|
|
||||||
|
var result = await NewController(service).GetRoles(CancellationToken.None);
|
||||||
|
|
||||||
|
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var roles = ((System.Collections.IEnumerable)ok.Value!).Cast<Roles_DTO>().ToList();
|
||||||
|
roles.Should().ContainSingle(r => r.Name == "Admin" && r.Description == "Full Access");
|
||||||
|
}
|
||||||
|
}
|
||||||
168
Api.SeaHavenIndustries.Tests/SettingsServiceTests.cs
Normal file
168
Api.SeaHavenIndustries.Tests/SettingsServiceTests.cs
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
using Data.SeaHavenIndustries;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using SeaHaven.DataServices.Implementation;
|
||||||
|
using SeaHaven.Services.DTOs;
|
||||||
|
using SeaHaven.Services.Implementation;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Api.SeaHavenIndustries.Tests;
|
||||||
|
|
||||||
|
public class SettingsServiceTests
|
||||||
|
{
|
||||||
|
private static ApplicationDbContext NewContext()
|
||||||
|
{
|
||||||
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||||
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||||
|
.Options;
|
||||||
|
return new ApplicationDbContext(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SettingsService NewService(ApplicationDbContext ctx) =>
|
||||||
|
new(new SettingsDataService(ctx), null!);
|
||||||
|
|
||||||
|
private static (ApplicationDbContext ctx, UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager) NewIdentityContext()
|
||||||
|
{
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddLogging();
|
||||||
|
services.AddDbContext<ApplicationDbContext>(o => o.UseInMemoryDatabase(Guid.NewGuid().ToString()));
|
||||||
|
services.AddIdentity<ApplicationUser, IdentityRole>().AddEntityFrameworkStores<ApplicationDbContext>();
|
||||||
|
var provider = services.BuildServiceProvider();
|
||||||
|
var scope = provider.CreateScope();
|
||||||
|
var ctx = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||||
|
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
|
||||||
|
return (ctx, userManager, roleManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddCategoryAsync_PersistsName()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
await NewService(ctx).AddCategoryAsync("Plumbing", CancellationToken.None);
|
||||||
|
ctx.Categories.Single().Name.Should().Be("Plumbing");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateCategoryAsync_UpdatesNameAndReturnsFalseWhenMissing()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
ctx.Categories.Add(new Category { Name = "Old" });
|
||||||
|
ctx.SaveChanges();
|
||||||
|
|
||||||
|
var service = NewService(ctx);
|
||||||
|
(await service.UpdateCategoryAsync(1, "New", CancellationToken.None)).Should().BeTrue();
|
||||||
|
ctx.Categories.Single().Name.Should().Be("New");
|
||||||
|
(await service.UpdateCategoryAsync(999, "X", CancellationToken.None)).Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteCategoryAsync_CascadesTemplatesAndWorkOrderCategories()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
ctx.Categories.Add(new Category { Id = 1, Name = "C1" });
|
||||||
|
ctx.Templates.Add(new Template { CategoryId = 1, WorkerOrderTitle = "T" });
|
||||||
|
ctx.workOrderCategories.Add(new WorkOrderCategories { CategoryId = 1 });
|
||||||
|
ctx.SaveChanges();
|
||||||
|
|
||||||
|
var service = NewService(ctx);
|
||||||
|
(await service.DeleteCategoryAsync(1, CancellationToken.None)).Should().BeTrue();
|
||||||
|
(await service.DeleteCategoryAsync(1, CancellationToken.None)).Should().BeFalse();
|
||||||
|
|
||||||
|
ctx.Categories.Should().BeEmpty();
|
||||||
|
ctx.Templates.Should().BeEmpty();
|
||||||
|
ctx.workOrderCategories.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetCategoryListAsync_MapsToServiceDtoAndPages()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
ctx.Categories.Add(new Category { Name = "A" });
|
||||||
|
ctx.Categories.Add(new Category { Name = "B" });
|
||||||
|
ctx.SaveChanges();
|
||||||
|
|
||||||
|
var (items, total) = await NewService(ctx).GetCategoryListAsync(1, 10, CancellationToken.None);
|
||||||
|
|
||||||
|
total.Should().Be(2);
|
||||||
|
items.Should().OnlyContain(c => c is SettingsCategoryDTO);
|
||||||
|
items.Select(c => c.Name).Should().Contain(new[] { "A", "B" });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetTemplatesAsync_MapsWorkOrderScalarsToTypedDto()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
var wo = new WorkOrder
|
||||||
|
{
|
||||||
|
istemplate = true,
|
||||||
|
InternalWONumber = "WO-100",
|
||||||
|
WorkerOrderTitle = "Template A",
|
||||||
|
Status = "Active",
|
||||||
|
Priority = "High",
|
||||||
|
Trade = "HVAC",
|
||||||
|
VendorNTE = 1500m,
|
||||||
|
Customer = "Cust"
|
||||||
|
};
|
||||||
|
ctx.workOrders.Add(wo);
|
||||||
|
ctx.SaveChanges();
|
||||||
|
|
||||||
|
var (items, total) = await NewService(ctx).GetTemplatesAsync(1, 10, CancellationToken.None);
|
||||||
|
|
||||||
|
total.Should().Be(1);
|
||||||
|
var dto = items.Single();
|
||||||
|
dto.Should().BeOfType<SettingsTemplateDTO>();
|
||||||
|
dto.InternalWONumber.Should().Be("WO-100");
|
||||||
|
dto.WorkerOrderTitle.Should().Be("Template A");
|
||||||
|
dto.Status.Should().Be("Active");
|
||||||
|
dto.Priority.Should().Be("High");
|
||||||
|
dto.Trade.Should().Be("HVAC");
|
||||||
|
dto.VendorNTE.Should().Be(1500m);
|
||||||
|
dto.Customer.Should().Be("Cust");
|
||||||
|
dto.istemplate.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetUsersAsync_ExcludesAdminsAndResolvesRole()
|
||||||
|
{
|
||||||
|
var (ctx, userManager, roleManager) = NewIdentityContext();
|
||||||
|
var t1 = roleManager.CreateAsync(new IdentityRole("Admin"));
|
||||||
|
var t2 = roleManager.CreateAsync(new IdentityRole("User"));
|
||||||
|
await Task.WhenAll(t1, t2);
|
||||||
|
|
||||||
|
var admin = new ApplicationUser { UserName = "admin1", Email = "a@x.com", FirstName = "Boss" };
|
||||||
|
var worker = new ApplicationUser { UserName = "worker1", Email = "w@x.com", FirstName = "Hand" };
|
||||||
|
await userManager.CreateAsync(admin);
|
||||||
|
await userManager.CreateAsync(worker);
|
||||||
|
await userManager.AddToRoleAsync(admin, "Admin");
|
||||||
|
await userManager.AddToRoleAsync(worker, "User");
|
||||||
|
|
||||||
|
ctx.SaveChanges();
|
||||||
|
|
||||||
|
var service = new SettingsService(new SettingsDataService(ctx), userManager);
|
||||||
|
var (users, total) = await service.GetUsersAsync("", 1, 10, CancellationToken.None);
|
||||||
|
|
||||||
|
total.Should().BeGreaterThanOrEqualTo(2);
|
||||||
|
users.Should().NotContain(u => u.RoleName == "Admin");
|
||||||
|
users.Should().Contain(u => u.UserName == "Hand" && u.RoleName == "User");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetRolesAsync_MapsKnownRoleNamesToDescriptions()
|
||||||
|
{
|
||||||
|
using var ctx = NewContext();
|
||||||
|
ctx.Roles.Add(new IdentityRole("Admin"));
|
||||||
|
ctx.Roles.Add(new IdentityRole("Manager"));
|
||||||
|
ctx.Roles.Add(new IdentityRole("Unknown"));
|
||||||
|
ctx.SaveChanges();
|
||||||
|
|
||||||
|
var roles = await NewService(ctx).GetRolesAsync(CancellationToken.None);
|
||||||
|
|
||||||
|
var byName = roles.ToDictionary(r => r.Name!, r => r.Description);
|
||||||
|
byName["Admin"].Should().Be("Full Access");
|
||||||
|
byName["Manager"].Should().Be("Full access accept quotes");
|
||||||
|
roles.Should().NotContain(r => r.Name == "Unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
using Data.SeaHavenIndustries;
|
using Data.SeaHavenIndustries;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using SeaHaven.Services.Interfaces;
|
using SeaHaven.Services.Interfaces;
|
||||||
using SeaHaven.Services.DTOs;
|
|
||||||
|
|
||||||
namespace Api.SeaHavenIndustries.Controllers
|
namespace Api.SeaHavenIndustries.Controllers
|
||||||
{
|
{
|
||||||
|
|
@ -14,12 +12,10 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
public class DispatchController : Controller
|
public class DispatchController : Controller
|
||||||
{
|
{
|
||||||
private readonly IDispatchService _dispatchService;
|
private readonly IDispatchService _dispatchService;
|
||||||
private readonly ApplicationDbContext _db; // Keep for complex queries
|
|
||||||
|
|
||||||
public DispatchController(IDispatchService dispatchService, ApplicationDbContext db)
|
public DispatchController(IDispatchService dispatchService)
|
||||||
{
|
{
|
||||||
_dispatchService = dispatchService;
|
_dispatchService = dispatchService;
|
||||||
_db = db;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|
@ -32,317 +28,29 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
[FromQuery] string? sortBy = "dispatchedAt",
|
[FromQuery] string? sortBy = "dispatchedAt",
|
||||||
[FromQuery] string? sortDir = "desc",
|
[FromQuery] string? sortDir = "desc",
|
||||||
[FromQuery] int page = 1,
|
[FromQuery] int page = 1,
|
||||||
[FromQuery] int pageSize = 25)
|
[FromQuery] int pageSize = 25,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var q = _db.Dispatches
|
var result = await _dispatchService.GetDispatchListAsync(
|
||||||
.Include(d => d.Vendor)
|
search, status, vendorId, dateFrom, dateTo, sortBy, sortDir, page, pageSize, cancellationToken);
|
||||||
.Include(d => d.WorkOrder).ThenInclude(w => w!.Locations)
|
|
||||||
.Where(d => d.IsDeleted == null || d.IsDeleted == false);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(search))
|
|
||||||
{
|
|
||||||
var s = search.ToLower();
|
|
||||||
q = q.Where(d =>
|
|
||||||
(d.DispatchNumber ?? "").ToLower().Contains(s) ||
|
|
||||||
(d.PONumber ?? "").ToLower().Contains(s) ||
|
|
||||||
(d.Vendor != null && (d.Vendor.CompanyName ?? "").ToLower().Contains(s)) ||
|
|
||||||
(d.WorkOrder != null && (d.WorkOrder.InternalWONumber ?? "").ToLower().Contains(s)) ||
|
|
||||||
(d.WorkOrder != null && (d.WorkOrder.WorkerOrderTitle ?? "").ToLower().Contains(s)) ||
|
|
||||||
(d.WorkOrder != null && d.WorkOrder.Locations != null && (d.WorkOrder.Locations.Name ?? "").ToLower().Contains(s))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(status))
|
|
||||||
q = q.Where(d => d.Status == status);
|
|
||||||
|
|
||||||
if (vendorId.HasValue)
|
|
||||||
q = q.Where(d => d.VendorId == vendorId.Value);
|
|
||||||
|
|
||||||
if (dateFrom.HasValue)
|
|
||||||
q = q.Where(d => d.DispatchedAt >= dateFrom.Value);
|
|
||||||
|
|
||||||
if (dateTo.HasValue)
|
|
||||||
{
|
|
||||||
var end = dateTo.Value.Date.AddDays(1);
|
|
||||||
q = q.Where(d => d.DispatchedAt < end);
|
|
||||||
}
|
|
||||||
|
|
||||||
var total = await q.CountAsync();
|
|
||||||
|
|
||||||
var dir = (sortDir ?? "desc").ToLowerInvariant() == "asc";
|
|
||||||
switch ((sortBy ?? "").ToLowerInvariant())
|
|
||||||
{
|
|
||||||
case "ponumber":
|
|
||||||
q = dir ? q.OrderBy(d => d.PONumber) : q.OrderByDescending(d => d.PONumber);
|
|
||||||
break;
|
|
||||||
case "dispatchnumber":
|
|
||||||
q = dir ? q.OrderBy(d => d.DispatchNumber) : q.OrderByDescending(d => d.DispatchNumber);
|
|
||||||
break;
|
|
||||||
case "vendor":
|
|
||||||
q = dir ? q.OrderBy(d => d.Vendor!.CompanyName) : q.OrderByDescending(d => d.Vendor!.CompanyName);
|
|
||||||
break;
|
|
||||||
case "nte":
|
|
||||||
case "nteamount":
|
|
||||||
q = dir ? q.OrderBy(d => d.NTEAmount) : q.OrderByDescending(d => d.NTEAmount);
|
|
||||||
break;
|
|
||||||
case "status":
|
|
||||||
q = dir ? q.OrderBy(d => d.Status) : q.OrderByDescending(d => d.Status);
|
|
||||||
break;
|
|
||||||
case "scheduleddate":
|
|
||||||
q = dir ? q.OrderBy(d => d.ScheduledDate) : q.OrderByDescending(d => d.ScheduledDate);
|
|
||||||
break;
|
|
||||||
case "completeddate":
|
|
||||||
q = dir ? q.OrderBy(d => d.CompletedDate) : q.OrderByDescending(d => d.CompletedDate);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
q = dir ? q.OrderBy(d => d.DispatchedAt) : q.OrderByDescending(d => d.DispatchedAt);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var rows = await q
|
|
||||||
.Skip((Math.Max(page, 1) - 1) * pageSize)
|
|
||||||
.Take(pageSize)
|
|
||||||
.Select(d => new
|
|
||||||
{
|
|
||||||
d.Id,
|
|
||||||
d.DispatchNumber,
|
|
||||||
d.PONumber,
|
|
||||||
d.NTEAmount,
|
|
||||||
d.Status,
|
|
||||||
d.DispatchedAt,
|
|
||||||
d.ScheduledDate,
|
|
||||||
d.CompletedDate,
|
|
||||||
d.AcknowledgedAt,
|
|
||||||
d.VerifiedAt,
|
|
||||||
VendorId = d.VendorId,
|
|
||||||
VendorName = d.Vendor != null ? d.Vendor.CompanyName : null,
|
|
||||||
WorkOrderId = d.WorkOrderId,
|
|
||||||
PrimaryWOInternalNumber = d.WorkOrder != null ? d.WorkOrder.InternalWONumber : null,
|
|
||||||
PrimaryWOTitle = d.WorkOrder != null ? d.WorkOrder.WorkerOrderTitle : null,
|
|
||||||
LocationName = d.WorkOrder != null && d.WorkOrder.Locations != null ? d.WorkOrder.Locations.Name : null,
|
|
||||||
WorkOrderCount = _db.DispatchWorkOrders.Count(dwo => dwo.DispatchId == d.Id),
|
|
||||||
PendingUpliftCount = _db.DispatchUpliftRequests.Count(u => u.DispatchId == d.Id && u.Status == "Pending" && (u.IsDeleted == null || u.IsDeleted == false))
|
|
||||||
})
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
return Ok(new DataResponse
|
return Ok(new DataResponse
|
||||||
{
|
{
|
||||||
Status = "Success",
|
Status = "Success",
|
||||||
Data = new { total, page, pageSize, items = rows }
|
Data = new { total = result.Total, page = result.Page, pageSize = result.PageSize, items = result.Items }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id:int}")]
|
[HttpGet("{id:int}")]
|
||||||
public async Task<IActionResult> GetById(int id)
|
public async Task<IActionResult> GetById(int id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var dispatch = await _db.Dispatches
|
var dispatch = await _dispatchService.GetDispatchDetailAsync(id, cancellationToken);
|
||||||
.Include(d => d.Vendor)
|
|
||||||
.Include(d => d.WorkOrder).ThenInclude(w => w!.Locations)
|
|
||||||
.FirstOrDefaultAsync(d => d.Id == id);
|
|
||||||
if (dispatch == null) return NotFound(new Response { Status = "Error", Message = "Dispatch not found" });
|
if (dispatch == null) return NotFound(new Response { Status = "Error", Message = "Dispatch not found" });
|
||||||
|
|
||||||
var linkedWOs = await _db.DispatchWorkOrders
|
|
||||||
.Where(dwo => dwo.DispatchId == id)
|
|
||||||
.Select(dwo => new
|
|
||||||
{
|
|
||||||
dwo.WorkOrderId,
|
|
||||||
InternalWONumber = dwo.WorkOrder != null ? dwo.WorkOrder.InternalWONumber : null,
|
|
||||||
Title = dwo.WorkOrder != null ? dwo.WorkOrder.WorkerOrderTitle : null,
|
|
||||||
Priority = dwo.WorkOrder != null ? dwo.WorkOrder.Priority : null,
|
|
||||||
Status = dwo.WorkOrder != null ? dwo.WorkOrder.Status : null,
|
|
||||||
LocationName = dwo.WorkOrder != null && dwo.WorkOrder.Locations != null ? dwo.WorkOrder.Locations.Name : null,
|
|
||||||
Trade = dwo.WorkOrder != null ? dwo.WorkOrder.Trade : null,
|
|
||||||
SubTrade = dwo.WorkOrder != null ? dwo.WorkOrder.SubTrade : null,
|
|
||||||
Problem = dwo.WorkOrder != null ? dwo.WorkOrder.Problem : null
|
|
||||||
}).ToListAsync();
|
|
||||||
|
|
||||||
if (linkedWOs.Count == 0 && dispatch.WorkOrderId.HasValue)
|
|
||||||
{
|
|
||||||
var wo = await _db.workOrders
|
|
||||||
.Include(w => w.Locations)
|
|
||||||
.FirstOrDefaultAsync(w => w.Id == dispatch.WorkOrderId.Value);
|
|
||||||
if (wo != null)
|
|
||||||
{
|
|
||||||
linkedWOs = new[] { new
|
|
||||||
{
|
|
||||||
WorkOrderId = wo.Id,
|
|
||||||
InternalWONumber = wo.InternalWONumber,
|
|
||||||
Title = wo.WorkerOrderTitle,
|
|
||||||
Priority = wo.Priority,
|
|
||||||
Status = wo.Status,
|
|
||||||
LocationName = wo.Locations != null ? wo.Locations.Name : null,
|
|
||||||
Trade = wo.Trade,
|
|
||||||
SubTrade = wo.SubTrade,
|
|
||||||
Problem = wo.Problem
|
|
||||||
}}.ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var checklist = await _db.DispatchChecklistItems
|
|
||||||
.Where(c => c.DispatchId == id && (c.IsDeleted == null || c.IsDeleted == false))
|
|
||||||
.OrderBy(c => c.SortOrder)
|
|
||||||
.Select(c => new { c.Id, c.ItemText, c.IsCompleted, c.CompletedBy, c.CompletedAt, c.SortOrder, c.WorkOrderId })
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var signoffs = await _db.DispatchSignoffs
|
|
||||||
.Where(s => s.DispatchId == id && (s.IsDeleted == null || s.IsDeleted == false))
|
|
||||||
.Select(s => new { s.Id, s.SignoffType, s.Name, s.Signature, s.SignatureMethod, s.SignedAt })
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var rawComments = await (from c in _db.Comments
|
|
||||||
where c.DispatchId == id && (c.IsDeleted == null || c.IsDeleted == false)
|
|
||||||
join u in _db.Users on c.UserId equals u.Id into users
|
|
||||||
from u in users.DefaultIfEmpty()
|
|
||||||
orderby c.CreatedDate
|
|
||||||
select new
|
|
||||||
{
|
|
||||||
c.Id,
|
|
||||||
c.Commenttext,
|
|
||||||
c.Commenter,
|
|
||||||
c.CommentType,
|
|
||||||
c.RecordType,
|
|
||||||
c.CreatedDate,
|
|
||||||
c.UserId,
|
|
||||||
UserFirstName = u != null ? u.FirstName : null,
|
|
||||||
UserLastName = u != null ? u.LastName : null
|
|
||||||
})
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var comments = rawComments.Select(c => new
|
|
||||||
{
|
|
||||||
c.Id,
|
|
||||||
c.Commenttext,
|
|
||||||
c.Commenter,
|
|
||||||
c.CommentType,
|
|
||||||
c.RecordType,
|
|
||||||
c.CreatedDate,
|
|
||||||
UserName = string.Join(" ", new[] { c.UserFirstName, c.UserLastName }
|
|
||||||
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim()
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
var rawUplifts = await (from u in _db.DispatchUpliftRequests
|
|
||||||
where u.DispatchId == id && (u.IsDeleted == null || u.IsDeleted == false)
|
|
||||||
join dec in _db.Users on u.DecidedByUserId equals dec.Id into decs
|
|
||||||
from dec in decs.DefaultIfEmpty()
|
|
||||||
orderby u.CreatedDate descending
|
|
||||||
select new
|
|
||||||
{
|
|
||||||
u.Id,
|
|
||||||
u.CurrentNTE,
|
|
||||||
u.RequestedNTE,
|
|
||||||
u.VendorReason,
|
|
||||||
u.Status,
|
|
||||||
u.RequiredTier,
|
|
||||||
u.RequestedByVendorName,
|
|
||||||
RequestedAt = u.CreatedDate,
|
|
||||||
u.DecidedAt,
|
|
||||||
u.DecisionNote,
|
|
||||||
DecidedByFirstName = dec != null ? dec.FirstName : null,
|
|
||||||
DecidedByLastName = dec != null ? dec.LastName : null
|
|
||||||
})
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var upliftRequests = rawUplifts.Select(u => new
|
|
||||||
{
|
|
||||||
u.Id,
|
|
||||||
u.CurrentNTE,
|
|
||||||
u.RequestedNTE,
|
|
||||||
Delta = u.RequestedNTE - (u.CurrentNTE ?? 0m),
|
|
||||||
u.VendorReason,
|
|
||||||
u.Status,
|
|
||||||
u.RequiredTier,
|
|
||||||
u.RequestedByVendorName,
|
|
||||||
u.RequestedAt,
|
|
||||||
u.DecidedAt,
|
|
||||||
u.DecisionNote,
|
|
||||||
DecidedByName = string.Join(" ", new[] { u.DecidedByFirstName, u.DecidedByLastName }
|
|
||||||
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim()
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
var dispatchNumber = dispatch.DispatchNumber ?? "";
|
|
||||||
var workOrderIds = linkedWOs.Select(l => l.WorkOrderId).Where(wid => wid > 0).ToList();
|
|
||||||
|
|
||||||
var rawAudit = await (from a in _db.WorkOrderAuditLogs
|
|
||||||
where workOrderIds.Contains(a.WorkOrderId)
|
|
||||||
&& a.FieldName != null
|
|
||||||
&& a.FieldName.Contains(dispatchNumber)
|
|
||||||
join u in _db.Users on a.UserId equals u.Id into users
|
|
||||||
from u in users.DefaultIfEmpty()
|
|
||||||
orderby a.CreatedAt descending
|
|
||||||
select new
|
|
||||||
{
|
|
||||||
a.Id,
|
|
||||||
a.Action,
|
|
||||||
a.FieldName,
|
|
||||||
a.OldValue,
|
|
||||||
a.NewValue,
|
|
||||||
a.CreatedAt,
|
|
||||||
a.UserId,
|
|
||||||
UserFirstName = u != null ? u.FirstName : null,
|
|
||||||
UserLastName = u != null ? u.LastName : null
|
|
||||||
})
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var auditLog = rawAudit.Select(a => new
|
|
||||||
{
|
|
||||||
Id = a.Id,
|
|
||||||
Action = a.Action,
|
|
||||||
FieldName = a.FieldName,
|
|
||||||
OldValue = a.OldValue,
|
|
||||||
NewValue = a.NewValue,
|
|
||||||
CreatedAt = a.CreatedAt,
|
|
||||||
UserName = string.Join(" ", new[] { a.UserFirstName, a.UserLastName }
|
|
||||||
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim()
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
return Ok(new DataResponse
|
return Ok(new DataResponse
|
||||||
{
|
{
|
||||||
Status = "Success",
|
Status = "Success",
|
||||||
Data = new
|
Data = dispatch
|
||||||
{
|
|
||||||
dispatch.Id,
|
|
||||||
dispatch.DispatchNumber,
|
|
||||||
dispatch.PONumber,
|
|
||||||
dispatch.NTEAmount,
|
|
||||||
dispatch.Description,
|
|
||||||
dispatch.Status,
|
|
||||||
dispatch.ScheduledDate,
|
|
||||||
dispatch.CompletedDate,
|
|
||||||
dispatch.DispatchedAt,
|
|
||||||
dispatch.AcknowledgedAt,
|
|
||||||
dispatch.VerifiedAt,
|
|
||||||
dispatch.VerifiedBy,
|
|
||||||
dispatch.EmailSent,
|
|
||||||
dispatch.WorkOrderId,
|
|
||||||
Vendor = dispatch.Vendor == null ? null : new
|
|
||||||
{
|
|
||||||
dispatch.Vendor.Id,
|
|
||||||
dispatch.Vendor.CompanyName,
|
|
||||||
dispatch.Vendor.ContactName,
|
|
||||||
dispatch.Vendor.Email,
|
|
||||||
dispatch.Vendor.Phone,
|
|
||||||
dispatch.Vendor.Address,
|
|
||||||
dispatch.Vendor.City,
|
|
||||||
dispatch.Vendor.State,
|
|
||||||
dispatch.Vendor.Zip,
|
|
||||||
dispatch.Vendor.TradeSpecialties
|
|
||||||
},
|
|
||||||
PrimaryLocation = dispatch.WorkOrder?.Locations == null ? null : new
|
|
||||||
{
|
|
||||||
dispatch.WorkOrder.Locations.Id,
|
|
||||||
dispatch.WorkOrder.Locations.Name,
|
|
||||||
dispatch.WorkOrder.Locations.Address1,
|
|
||||||
dispatch.WorkOrder.Locations.City,
|
|
||||||
dispatch.WorkOrder.Locations.State,
|
|
||||||
dispatch.WorkOrder.Locations.Zip
|
|
||||||
},
|
|
||||||
WorkOrders = linkedWOs,
|
|
||||||
Checklist = checklist,
|
|
||||||
Signoffs = signoffs,
|
|
||||||
Comments = comments,
|
|
||||||
UpliftRequests = upliftRequests,
|
|
||||||
AuditLog = auditLog
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,9 @@ using Api.SeaHavenIndustries.DTOs;
|
||||||
using Data.SeaHavenIndustries;
|
using Data.SeaHavenIndustries;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using SeaHaven.Services.Interfaces;
|
|
||||||
using SeaHaven.Services.DTOs;
|
using SeaHaven.Services.DTOs;
|
||||||
using SeaHaven.DataServices.Interfaces;
|
using SeaHaven.Services.Interfaces;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
|
|
@ -17,16 +14,10 @@ using SeaHaven.DataServices.Interfaces;
|
||||||
public class FollowUpController : ControllerBase
|
public class FollowUpController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IFollowUpService _followUpService;
|
private readonly IFollowUpService _followUpService;
|
||||||
private readonly IFollowUpDataService _followUpDataService; // Keep for direct entity operations
|
|
||||||
private readonly ApplicationDbContext _db; // Keep for User lookups
|
|
||||||
private readonly UserManager<ApplicationUser> _users;
|
|
||||||
|
|
||||||
public FollowUpController(IFollowUpService followUpService, IFollowUpDataService followUpDataService, ApplicationDbContext db, UserManager<ApplicationUser> users)
|
public FollowUpController(IFollowUpService followUpService)
|
||||||
{
|
{
|
||||||
_followUpService = followUpService;
|
_followUpService = followUpService;
|
||||||
_followUpDataService = followUpDataService;
|
|
||||||
_db = db;
|
|
||||||
_users = users;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("GetFollowUpList")]
|
[HttpGet("GetFollowUpList")]
|
||||||
|
|
@ -34,70 +25,31 @@ public class FollowUpController : ControllerBase
|
||||||
string? search = "",
|
string? search = "",
|
||||||
string? status = "Open",
|
string? status = "Open",
|
||||||
int page = 1,
|
int page = 1,
|
||||||
int pageSize = 12)
|
int pageSize = 12,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
status = string.IsNullOrWhiteSpace(status) ? "Open" : status;
|
var pagedResult = await _followUpService.GetFollowUpListAsync(page, pageSize, search, status, cancellationToken);
|
||||||
|
|
||||||
var pagedResult = await _followUpService.GetFollowUpsPagedAsync(page, pageSize, search, status);
|
|
||||||
var items = pagedResult.Items;
|
var items = pagedResult.Items;
|
||||||
|
|
||||||
var rows = items.Select(x => new
|
var data = items.Select(x => new
|
||||||
{
|
{
|
||||||
Id = x.Id,
|
x.Id,
|
||||||
Reason = x.Reason,
|
x.Reason,
|
||||||
ScheduleDate = x.ScheduleDate,
|
x.ScheduleDate,
|
||||||
ScheduleStartTime = x.ScheduleStartTime,
|
x.ScheduleStartTime,
|
||||||
EmployeeId = x.EmployeeId,
|
x.EmployeeId,
|
||||||
WorkOrderId = x.WorkOrderId,
|
x.WorkOrderId,
|
||||||
AccountId = x.AccountId,
|
x.AccountId,
|
||||||
LocationId = x.LocationId,
|
x.LocationId,
|
||||||
AccountName = x.AccountName ?? "",
|
x.AccountName,
|
||||||
AssignedTo = x.EmployeeName ?? "",
|
x.AssignedTo,
|
||||||
LocationName = x.LocationName ?? "",
|
x.LocationName,
|
||||||
WorkOrderNumber = "",
|
x.WorkOrderNumber,
|
||||||
InternalWONumber = "",
|
x.InternalWONumber,
|
||||||
Status = x.Status,
|
x.Status,
|
||||||
CreatedDate = x.CreatedDate,
|
x.CreatedDate,
|
||||||
CreatedById = (string?)null
|
x.CreatedById,
|
||||||
}).ToList();
|
x.CreatedByName
|
||||||
|
|
||||||
var creatorIds = rows
|
|
||||||
.Select(r => r.CreatedById)
|
|
||||||
.Where(id => !string.IsNullOrEmpty(id))
|
|
||||||
.Distinct()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var creators = await _db.Users
|
|
||||||
.Where(u => creatorIds.Contains(u.Id))
|
|
||||||
.Select(u => new { u.Id, u.FirstName, u.LastName, u.UserName })
|
|
||||||
.ToDictionaryAsync(u => u.Id, u => u);
|
|
||||||
|
|
||||||
string NameFor(string? userId)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(userId) || !creators.TryGetValue(userId, out var u)) return "";
|
|
||||||
var full = ((u.FirstName ?? "") + " " + (u.LastName ?? "")).Trim();
|
|
||||||
return string.IsNullOrWhiteSpace(full) ? (u.UserName ?? "") : full;
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = rows.Select(r => new
|
|
||||||
{
|
|
||||||
r.Id,
|
|
||||||
r.Reason,
|
|
||||||
r.ScheduleDate,
|
|
||||||
r.ScheduleStartTime,
|
|
||||||
r.EmployeeId,
|
|
||||||
r.WorkOrderId,
|
|
||||||
r.AccountId,
|
|
||||||
r.LocationId,
|
|
||||||
r.AccountName,
|
|
||||||
r.AssignedTo,
|
|
||||||
r.LocationName,
|
|
||||||
r.WorkOrderNumber,
|
|
||||||
r.InternalWONumber,
|
|
||||||
r.Status,
|
|
||||||
r.CreatedDate,
|
|
||||||
r.CreatedById,
|
|
||||||
CreatedByName = NameFor(r.CreatedById)
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
return Ok(new Pagination_DTO
|
return Ok(new Pagination_DTO
|
||||||
|
|
@ -111,26 +63,12 @@ public class FollowUpController : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("GetById")]
|
[HttpGet("GetById")]
|
||||||
public async Task<IActionResult> GetById(int id)
|
public async Task<IActionResult> GetById(int id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var model = await _followUpDataService.GetByIdWithDetailsAsync(id);
|
var model = await _followUpService.GetFollowUpDetailAsync(id, cancellationToken);
|
||||||
|
|
||||||
if (model == null) return NotFound();
|
if (model == null) return NotFound();
|
||||||
|
|
||||||
string createdByName = "";
|
|
||||||
if (!string.IsNullOrEmpty(model.createdby))
|
|
||||||
{
|
|
||||||
var u = await _db.Users
|
|
||||||
.Where(x => x.Id == model.createdby)
|
|
||||||
.Select(x => new { x.FirstName, x.LastName, x.UserName })
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
if (u != null)
|
|
||||||
{
|
|
||||||
var n = ((u.FirstName ?? "") + " " + (u.LastName ?? "")).Trim();
|
|
||||||
createdByName = string.IsNullOrWhiteSpace(n) ? (u.UserName ?? "") : n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(new
|
return Ok(new
|
||||||
{
|
{
|
||||||
model.Id,
|
model.Id,
|
||||||
|
|
@ -141,22 +79,20 @@ public class FollowUpController : ControllerBase
|
||||||
model.WorkOrderId,
|
model.WorkOrderId,
|
||||||
model.AccountId,
|
model.AccountId,
|
||||||
model.LocationId,
|
model.LocationId,
|
||||||
AccountName = model.Account?.Name ?? "",
|
model.AccountName,
|
||||||
AssignedTo = model.Employee != null
|
model.AssignedTo,
|
||||||
? (model.Employee.FirstName + " " + model.Employee.LastName)
|
model.LocationName,
|
||||||
: "",
|
model.WorkOrderNumber,
|
||||||
LocationName = model.Location?.Name ?? "",
|
model.InternalWONumber,
|
||||||
WorkOrderNumber = model.WorkOrder?.WorkerOrderNumber ?? "",
|
|
||||||
InternalWONumber = model.WorkOrder?.InternalWONumber ?? "",
|
|
||||||
model.Status,
|
model.Status,
|
||||||
model.CreatedDate,
|
model.CreatedDate,
|
||||||
CreatedById = model.createdby,
|
model.CreatedById,
|
||||||
CreatedByName = createdByName
|
model.CreatedByName
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("Create")]
|
[HttpPost("Create")]
|
||||||
public async Task<IActionResult> Create(FollowUp_DTO dto)
|
public async Task<IActionResult> Create(FollowUp_DTO dto, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -164,23 +100,9 @@ public class FollowUpController : ControllerBase
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||||
|
|
||||||
var model = new FollowUps
|
var id = await _followUpService.CreateOpenFollowUpAsync(MapToCreateRequest(dto), userId, cancellationToken);
|
||||||
{
|
|
||||||
Reason = dto.Reason,
|
|
||||||
ScheduleDate = dto.ScheduleDate,
|
|
||||||
ScheduleStartTime = dto.ScheduleStartTime,
|
|
||||||
EmployeeId = dto.EmployeeId,
|
|
||||||
WorkOrderId = dto.WorkOrderId,
|
|
||||||
AccountId = dto.AccountId,
|
|
||||||
LocationId = dto.LocationId,
|
|
||||||
Status = "Open",
|
|
||||||
CreatedDate = DateTime.UtcNow,
|
|
||||||
createdby = userId
|
|
||||||
};
|
|
||||||
|
|
||||||
await _followUpDataService.AddAsync(model);
|
return Ok(new DataResponse { Status = "200", Message = "Follow Up created", Data = id });
|
||||||
|
|
||||||
return Ok(new DataResponse { Status = "200", Message = "Follow Up created", Data = model.Id });
|
|
||||||
}
|
}
|
||||||
catch (ValidationException vex)
|
catch (ValidationException vex)
|
||||||
{
|
{
|
||||||
|
|
@ -194,24 +116,11 @@ public class FollowUpController : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("Update")]
|
[HttpPost("Update")]
|
||||||
public async Task<IActionResult> Update(FollowUp_DTO dto)
|
public async Task<IActionResult> Update(FollowUp_DTO dto, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var model = await _followUpDataService.GetByIdAsync(dto.Id);
|
await _followUpService.UpdateFollowUpFieldsAsync(dto.Id, MapToUpdateRequest(dto), cancellationToken);
|
||||||
if (model == null)
|
|
||||||
return NotFound(new Response { Status = "Error", Message = "Follow Up not found" });
|
|
||||||
|
|
||||||
model.Reason = dto.Reason;
|
|
||||||
model.ScheduleDate = dto.ScheduleDate;
|
|
||||||
model.ScheduleStartTime = dto.ScheduleStartTime;
|
|
||||||
model.EmployeeId = dto.EmployeeId;
|
|
||||||
model.WorkOrderId = dto.WorkOrderId;
|
|
||||||
model.AccountId = dto.AccountId;
|
|
||||||
model.LocationId = dto.LocationId;
|
|
||||||
model.LastModificationTime = DateTime.UtcNow;
|
|
||||||
|
|
||||||
await _followUpDataService.UpdateAsync(model);
|
|
||||||
return Ok(new DataResponse { Status = "200", Message = "Follow Up updated" });
|
return Ok(new DataResponse { Status = "200", Message = "Follow Up updated" });
|
||||||
}
|
}
|
||||||
catch (ValidationException vex)
|
catch (ValidationException vex)
|
||||||
|
|
@ -230,42 +139,32 @@ public class FollowUpController : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("Complete")]
|
[HttpPost("Complete")]
|
||||||
public async Task<IActionResult> Complete(int id)
|
public async Task<IActionResult> Complete(int id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var model = await _followUpDataService.GetByIdAsync(id);
|
var found = await _followUpService.SetFollowUpStatusAsync(id, "Completed", cancellationToken);
|
||||||
if (model == null) return NotFound();
|
if (!found) return NotFound();
|
||||||
|
|
||||||
model.Status = "Completed";
|
|
||||||
model.LastModificationTime = DateTime.UtcNow;
|
|
||||||
await _followUpDataService.UpdateAsync(model);
|
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("Cancel")]
|
[HttpPost("Cancel")]
|
||||||
public async Task<IActionResult> Cancel(int id)
|
public async Task<IActionResult> Cancel(int id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var model = await _followUpDataService.GetByIdAsync(id);
|
var found = await _followUpService.SetFollowUpStatusAsync(id, "Cancelled", cancellationToken);
|
||||||
if (model == null) return NotFound();
|
if (!found) return NotFound();
|
||||||
|
|
||||||
model.Status = "Cancelled";
|
|
||||||
model.LastModificationTime = DateTime.UtcNow;
|
|
||||||
await _followUpDataService.UpdateAsync(model);
|
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("Delete")]
|
[HttpPost("Delete")]
|
||||||
public async Task<IActionResult> Delete(int id)
|
public async Task<IActionResult> Delete(int id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var model = await _followUpDataService.GetByIdAsync(id);
|
var found = await _followUpService.RemoveFollowUpAsync(id, cancellationToken);
|
||||||
if (model == null)
|
if (!found)
|
||||||
return NotFound(new Response { Status = "Error", Message = "Follow Up not found" });
|
return NotFound(new Response { Status = "Error", Message = "Follow Up not found" });
|
||||||
|
|
||||||
await _followUpDataService.DeleteAsync(id);
|
|
||||||
|
|
||||||
return Ok(new DataResponse { Status = "200", Message = "Follow Up deleted" });
|
return Ok(new DataResponse { Status = "200", Message = "Follow Up deleted" });
|
||||||
}
|
}
|
||||||
catch (KeyNotFoundException)
|
catch (KeyNotFoundException)
|
||||||
|
|
@ -277,4 +176,33 @@ public class FollowUpController : ControllerBase
|
||||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static FollowUpCreateRequestDTO MapToCreateRequest(FollowUp_DTO dto)
|
||||||
|
{
|
||||||
|
return new FollowUpCreateRequestDTO
|
||||||
|
{
|
||||||
|
Reason = dto.Reason,
|
||||||
|
ScheduleDate = dto.ScheduleDate,
|
||||||
|
ScheduleStartTime = dto.ScheduleStartTime,
|
||||||
|
EmployeeId = dto.EmployeeId,
|
||||||
|
WorkOrderId = dto.WorkOrderId,
|
||||||
|
AccountId = dto.AccountId,
|
||||||
|
LocationId = dto.LocationId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FollowUpUpdateRequestDTO MapToUpdateRequest(FollowUp_DTO dto)
|
||||||
|
{
|
||||||
|
return new FollowUpUpdateRequestDTO
|
||||||
|
{
|
||||||
|
Id = dto.Id,
|
||||||
|
Reason = dto.Reason,
|
||||||
|
ScheduleDate = dto.ScheduleDate,
|
||||||
|
ScheduleStartTime = dto.ScheduleStartTime,
|
||||||
|
EmployeeId = dto.EmployeeId,
|
||||||
|
WorkOrderId = dto.WorkOrderId,
|
||||||
|
AccountId = dto.AccountId,
|
||||||
|
LocationId = dto.LocationId
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,8 @@ using Data.SeaHavenIndustries;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using SeaHaven.Services.Interfaces;
|
|
||||||
using SeaHaven.Services.DTOs;
|
using SeaHaven.Services.DTOs;
|
||||||
using SeaHaven.DataServices.Interfaces;
|
using SeaHaven.Services.Interfaces;
|
||||||
using System.Security.Claims;
|
|
||||||
|
|
||||||
namespace Api.SeaHavenIndustries.Controllers
|
namespace Api.SeaHavenIndustries.Controllers
|
||||||
{
|
{
|
||||||
|
|
@ -18,18 +15,16 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
public class LocationController : Controller
|
public class LocationController : Controller
|
||||||
{
|
{
|
||||||
private readonly ILocationService _locationService;
|
private readonly ILocationService _locationService;
|
||||||
private readonly ILocationDataService _locationDataService; // Keep for direct entity operations
|
|
||||||
|
|
||||||
public LocationController(ILocationService locationService, ILocationDataService locationDataService)
|
public LocationController(ILocationService locationService)
|
||||||
{
|
{
|
||||||
_locationService = locationService;
|
_locationService = locationService;
|
||||||
_locationDataService = locationDataService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("GetLocationList")]
|
[HttpGet("GetLocationList")]
|
||||||
public async Task<IActionResult> GetLocationList(string? search = "", int page = 1, int pageSize = 10)
|
public async Task<IActionResult> GetLocationList(string? search = "", int page = 1, int pageSize = 10, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var pagedResult = await _locationService.GetLocationsPagedAsync(page, pageSize, search);
|
var pagedResult = await _locationService.GetLocationListPagedAsync(page, pageSize, search, cancellationToken);
|
||||||
|
|
||||||
var data = pagedResult.Items.Select(l => new
|
var data = pagedResult.Items.Select(l => new
|
||||||
{
|
{
|
||||||
|
|
@ -61,9 +56,9 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
public async Task<IActionResult> GetLocationById(int id)
|
public async Task<IActionResult> GetLocationById(int id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var location = await _locationDataService.GetByIdAsync(id);
|
var location = await _locationService.GetLocationDetailAsync(id, cancellationToken);
|
||||||
|
|
||||||
if (location == null)
|
if (location == null)
|
||||||
return NotFound(new Response { Status = "Error", Message = "Location not found" });
|
return NotFound(new Response { Status = "Error", Message = "Location not found" });
|
||||||
|
|
@ -89,25 +84,11 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> AddLocation([FromBody] Location_DTO model)
|
public async Task<IActionResult> AddLocation([FromBody] Location_DTO model, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var location = new Locations
|
await _locationService.CreateLocationFromRequestAsync(MapToCreateRequest(model), cancellationToken);
|
||||||
{
|
|
||||||
Name = model.Name,
|
|
||||||
Title = model.Title,
|
|
||||||
Address1 = model.Address,
|
|
||||||
City = model.City,
|
|
||||||
State = model.State,
|
|
||||||
Zip = model.ZipCode,
|
|
||||||
PhoneNumber = model.Phone,
|
|
||||||
Email = model.ContactEmail,
|
|
||||||
Status = model.Status,
|
|
||||||
CreatedDate = DateTime.Now
|
|
||||||
};
|
|
||||||
|
|
||||||
await _locationDataService.AddAsync(location);
|
|
||||||
return Ok(new DataResponse { Message = "Location Created Successfully", Status = "200" });
|
return Ok(new DataResponse { Message = "Location Created Successfully", Status = "200" });
|
||||||
}
|
}
|
||||||
catch (ValidationException vex)
|
catch (ValidationException vex)
|
||||||
|
|
@ -122,26 +103,11 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{id}")]
|
[HttpPut("{id}")]
|
||||||
public async Task<IActionResult> EditLocation(int id, [FromBody] EditLocation_DTO model)
|
public async Task<IActionResult> EditLocation(int id, [FromBody] EditLocation_DTO model, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var exist = await _locationDataService.GetByIdAsync(id);
|
await _locationService.UpdateLocationFromRequestAsync(id, MapToUpdateRequest(model), cancellationToken);
|
||||||
if (exist == null)
|
|
||||||
return NotFound(new Response { Status = "Error", Message = "Location not found" });
|
|
||||||
|
|
||||||
exist.Name = model.Name;
|
|
||||||
exist.Title = model.Title;
|
|
||||||
exist.Address1 = model.Address;
|
|
||||||
exist.City = model.City;
|
|
||||||
exist.State = model.State;
|
|
||||||
exist.Zip = model.ZipCode;
|
|
||||||
exist.PhoneNumber = model.Phone;
|
|
||||||
exist.Email = model.ContactEmail;
|
|
||||||
exist.Status = model.Status;
|
|
||||||
exist.LastModificationTime = DateTime.Now;
|
|
||||||
|
|
||||||
await _locationDataService.UpdateAsync(exist);
|
|
||||||
return Ok(new DataResponse { Message = "Location Updated Successfully", Status = "200" });
|
return Ok(new DataResponse { Message = "Location Updated Successfully", Status = "200" });
|
||||||
}
|
}
|
||||||
catch (ValidationException vex)
|
catch (ValidationException vex)
|
||||||
|
|
@ -160,15 +126,14 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
||||||
public async Task<IActionResult> DeleteLocation(int id)
|
public async Task<IActionResult> DeleteLocation(int id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var exist = await _locationDataService.GetByIdAsync(id);
|
var found = await _locationService.DeleteLocationByIdAsync(id, cancellationToken);
|
||||||
if (exist == null)
|
if (!found)
|
||||||
return NotFound(new Response { Status = "Error", Message = "Location not found" });
|
return NotFound(new Response { Status = "Error", Message = "Location not found" });
|
||||||
|
|
||||||
await _locationDataService.DeleteAsync(id);
|
|
||||||
return Ok(new DataResponse { Message = "Location Deleted Successfully", Status = "200" });
|
return Ok(new DataResponse { Message = "Location Deleted Successfully", Status = "200" });
|
||||||
}
|
}
|
||||||
catch (KeyNotFoundException)
|
catch (KeyNotFoundException)
|
||||||
|
|
@ -182,9 +147,41 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("DeleteLocation")]
|
[HttpGet("DeleteLocation")]
|
||||||
public async Task<IActionResult> DeleteLocationByQuery(int id)
|
public async Task<IActionResult> DeleteLocationByQuery(int id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return await DeleteLocation(id);
|
return await DeleteLocation(id, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LocationCreateRequestDTO MapToCreateRequest(Location_DTO model)
|
||||||
|
{
|
||||||
|
return new LocationCreateRequestDTO
|
||||||
|
{
|
||||||
|
Name = model.Name,
|
||||||
|
Title = model.Title,
|
||||||
|
Address = model.Address,
|
||||||
|
City = model.City,
|
||||||
|
State = model.State,
|
||||||
|
ZipCode = model.ZipCode,
|
||||||
|
Phone = model.Phone,
|
||||||
|
ContactEmail = model.ContactEmail,
|
||||||
|
Status = model.Status
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LocationUpdateRequestDTO MapToUpdateRequest(EditLocation_DTO model)
|
||||||
|
{
|
||||||
|
return new LocationUpdateRequestDTO
|
||||||
|
{
|
||||||
|
Name = model.Name,
|
||||||
|
Title = model.Title,
|
||||||
|
Address = model.Address,
|
||||||
|
City = model.City,
|
||||||
|
State = model.State,
|
||||||
|
ZipCode = model.ZipCode,
|
||||||
|
Phone = model.Phone,
|
||||||
|
ContactEmail = model.ContactEmail,
|
||||||
|
Status = model.Status
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,8 @@
|
||||||
using Api.SeaHavenIndustries.Helper.ViewModel;
|
using Api.SeaHavenIndustries.Helper.ViewModel;
|
||||||
using Data.SeaHavenIndustries;
|
using Data.SeaHavenIndustries;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using SeaHaven.Services.Interfaces;
|
||||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
|
||||||
|
|
||||||
namespace Api.SeaHavenIndustries.Controllers
|
namespace Api.SeaHavenIndustries.Controllers
|
||||||
{
|
{
|
||||||
|
|
@ -14,24 +12,20 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
[Route("[controller]")]
|
[Route("[controller]")]
|
||||||
public class SettingsController : Controller
|
public class SettingsController : Controller
|
||||||
{
|
{
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly ISettingsService _settingsService;
|
||||||
private readonly ApplicationDbContext _db;
|
|
||||||
public SettingsController(UserManager<ApplicationUser> userManager, ApplicationDbContext db)
|
public SettingsController(ISettingsService settingsService)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_settingsService = settingsService;
|
||||||
_db = db;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("AddCategory")]
|
[Route("AddCategory")]
|
||||||
public async Task<IActionResult> AddCategory(Category_DTO model)
|
public async Task<IActionResult> AddCategory(Category_DTO model, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Category category = new Category();
|
await _settingsService.AddCategoryAsync(model.Name, cancellationToken);
|
||||||
category.Name = model.Name;
|
|
||||||
_db.Categories.Add(category);
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
return Ok(new DataResponse { Message = "Successfully Saved Data", Status = "200" });
|
return Ok(new DataResponse { Message = "Successfully Saved Data", Status = "200" });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -42,16 +36,13 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("EditCategory")]
|
[Route("EditCategory")]
|
||||||
public async Task<IActionResult> EditCategory(EditCategory_DTO model)
|
public async Task<IActionResult> EditCategory(EditCategory_DTO model, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var exist = _db.Categories.Where(w => w.Id == model.Id).FirstOrDefault();
|
var found = await _settingsService.UpdateCategoryAsync(model.Id, model.Name, cancellationToken);
|
||||||
if (exist == null)
|
if (!found)
|
||||||
return BadRequest(new Response { Status = "Error", Message = "Category not found" });
|
return BadRequest(new Response { Status = "Error", Message = "Category not found" });
|
||||||
exist.Name = model.Name;
|
|
||||||
_db.Categories.Update(exist);
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
|
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -62,30 +53,13 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("DeleteCategory")]
|
[Route("DeleteCategory")]
|
||||||
public async Task<IActionResult> DeleteCategory(int id)
|
public async Task<IActionResult> DeleteCategory(int id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var exist = _db.Categories.Where(w => w.Id == id).FirstOrDefault();
|
var found = await _settingsService.DeleteCategoryAsync(id, cancellationToken);
|
||||||
if (exist != null)
|
if (!found)
|
||||||
{
|
|
||||||
var chkexist = _db.Templates.Where(w => w.CategoryId == id).ToList();
|
|
||||||
if (chkexist != null)
|
|
||||||
{
|
|
||||||
_db.RemoveRange(chkexist);
|
|
||||||
}
|
|
||||||
var chkworkordercategory = _db.workOrderCategories.Where(w => w.CategoryId == id).ToList();
|
|
||||||
if (chkworkordercategory != null)
|
|
||||||
{
|
|
||||||
_db.RemoveRange(chkworkordercategory);
|
|
||||||
}
|
|
||||||
_db.Remove(exist);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return BadRequest(new Response { Status = "Error", Message = "ID not matched!" });
|
return BadRequest(new Response { Status = "Error", Message = "ID not matched!" });
|
||||||
}
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
return Ok(new DataResponse { Message = "Deleted Successfully", Status = "200" });
|
return Ok(new DataResponse { Message = "Deleted Successfully", Status = "200" });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -94,27 +68,20 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("GetCategoryList")]
|
[Route("GetCategoryList")]
|
||||||
public IActionResult GetCategoryList(int page = 1, int pageSize = 10)
|
public async Task<IActionResult> GetCategoryList(int page = 1, int pageSize = 10, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var totalCount = _db.Categories.Count();
|
var (items, total) = await _settingsService.GetCategoryListAsync(page, pageSize, cancellationToken);
|
||||||
var data = _db.Categories.Select(s => new
|
|
||||||
{
|
|
||||||
s.Id,
|
|
||||||
s.Name
|
|
||||||
}).OrderByDescending(d => d.Id).Skip((page - 1) * pageSize)
|
|
||||||
.Take(pageSize).ToList();
|
|
||||||
|
|
||||||
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
var totalPages = (int)Math.Ceiling(total / (double)pageSize);
|
||||||
|
|
||||||
var viewModel = new Pagination_DTO
|
var viewModel = new Pagination_DTO
|
||||||
{
|
{
|
||||||
Data = data,
|
Data = items,
|
||||||
PageNumber = page,
|
PageNumber = page,
|
||||||
PageSize = pageSize,
|
PageSize = pageSize,
|
||||||
TotalCount = totalCount,
|
TotalCount = total,
|
||||||
TotalPages = totalPages
|
TotalPages = totalPages
|
||||||
};
|
};
|
||||||
return Ok(viewModel);
|
return Ok(viewModel);
|
||||||
|
|
@ -122,24 +89,20 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("GetTemplates")]
|
[Route("GetTemplates")]
|
||||||
public IActionResult GetTemplates(int page = 1, int pageSize = 10)
|
public async Task<IActionResult> GetTemplates(int page = 1, int pageSize = 10, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var totalCount = _db.workOrders.Where(w=> w.istemplate == true).Count();
|
var (items, total) = await _settingsService.GetTemplatesAsync(page, pageSize, cancellationToken);
|
||||||
var data = _db.workOrders.Where(w => w.istemplate == true).OrderByDescending(d => d.Id).Skip((page - 1) * pageSize)
|
|
||||||
.Take(pageSize).ToList();
|
|
||||||
|
|
||||||
// Calculate the number of pages
|
var totalPages = (int)Math.Ceiling(total / (double)pageSize);
|
||||||
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
|
||||||
|
|
||||||
// Create a view model containing the paged items and pagination metadata
|
|
||||||
var viewModel = new Pagination_DTO
|
var viewModel = new Pagination_DTO
|
||||||
{
|
{
|
||||||
Data = data,
|
Data = items,
|
||||||
PageNumber = page,
|
PageNumber = page,
|
||||||
PageSize = pageSize,
|
PageSize = pageSize,
|
||||||
TotalCount = totalCount,
|
TotalCount = total,
|
||||||
TotalPages = totalPages
|
TotalPages = totalPages
|
||||||
};
|
};
|
||||||
return Ok(viewModel);
|
return Ok(viewModel);
|
||||||
|
|
@ -152,42 +115,30 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("GetUsers")]
|
[Route("GetUsers")]
|
||||||
public async Task<IActionResult> GetUsers(string? search = "", int page = 1, int pageSize = 10)
|
public async Task<IActionResult> GetUsers(string? search = "", int page = 1, int pageSize = 10, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var totalCount = _db.Users.Count();
|
var (users, total) = await _settingsService.GetUsersAsync(search, page, pageSize, cancellationToken);
|
||||||
var allUsers = _db.Users.Where(w => (w.FirstName ?? "").ToLower().Contains((search ?? "").ToLower())).OrderByDescending(d => d.Id).Skip((page - 1) * pageSize)
|
|
||||||
.Take(pageSize).ToList();
|
|
||||||
|
|
||||||
// Calculate the number of pages
|
var filteredUsers = users.Select(u => new UserRoleViewModel
|
||||||
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
|
||||||
|
|
||||||
var filteredUsers = new List<UserRoleViewModel>();
|
|
||||||
|
|
||||||
foreach (var user in allUsers)
|
|
||||||
{
|
{
|
||||||
var roles = await _userManager.GetRolesAsync(user);
|
RoleName = u.RoleName,
|
||||||
if (!roles.Contains("Admin"))
|
Email = u.Email,
|
||||||
{
|
UserName = u.UserName,
|
||||||
filteredUsers.Add(new UserRoleViewModel
|
Date = u.Date,
|
||||||
{
|
Status = u.Status,
|
||||||
RoleName = roles.Count() > 0 ? roles.First() : "",
|
Id = u.Id
|
||||||
Email = user.Email,
|
}).ToList();
|
||||||
UserName = user.FirstName,
|
|
||||||
Date = user.CreatedDate?.Date.ToString(),
|
var totalPages = (int)Math.Ceiling(total / (double)pageSize);
|
||||||
Status = user.UniqueName,
|
|
||||||
Id = user.Id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Create a view model containing the paged items and pagination metadata
|
|
||||||
var viewModel = new Pagination_DTO
|
var viewModel = new Pagination_DTO
|
||||||
{
|
{
|
||||||
Data = filteredUsers,
|
Data = filteredUsers,
|
||||||
PageNumber = page,
|
PageNumber = page,
|
||||||
PageSize = pageSize,
|
PageSize = pageSize,
|
||||||
TotalCount = totalCount,
|
TotalCount = total,
|
||||||
TotalPages = totalPages
|
TotalPages = totalPages
|
||||||
};
|
};
|
||||||
return Ok(viewModel);
|
return Ok(viewModel);
|
||||||
|
|
@ -200,35 +151,13 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("GetRoles")]
|
[Route("GetRoles")]
|
||||||
public async Task<IActionResult> GetRoles()
|
public async Task<IActionResult> GetRoles(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var data = _db.Roles.ToList();
|
var roles = await _settingsService.GetRolesAsync(cancellationToken);
|
||||||
List<Roles_DTO> roles = new List<Roles_DTO>();
|
var result = roles.Select(r => new Roles_DTO { Name = r.Name, Description = r.Description }).ToList();
|
||||||
foreach (var role in data)
|
return Ok(result);
|
||||||
{
|
|
||||||
if (role.Name == "Admin")
|
|
||||||
{
|
|
||||||
roles.Add(new Roles_DTO { Name = role.Name, Description = "Full Access" });
|
|
||||||
}
|
|
||||||
else if(role.Name == "Supervisor")
|
|
||||||
{
|
|
||||||
roles.Add(new Roles_DTO { Name = role.Name, Description = "Access to Team's Work Orders Only" });
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(role.Name == "Manager")
|
|
||||||
{
|
|
||||||
roles.Add(new Roles_DTO { Name = role.Name, Description = "Full access accept quotes" });
|
|
||||||
|
|
||||||
}
|
|
||||||
else if(role.Name =="User")
|
|
||||||
{
|
|
||||||
roles.Add(new Roles_DTO { Name = role.Name, Description = "Access to Assigned Work Orders Only" });
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(roles);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
169
SeaHaven.DataServices/Dto/DispatchReadModels.cs
Normal file
169
SeaHaven.DataServices/Dto/DispatchReadModels.cs
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
namespace SeaHaven.DataServices.Dto
|
||||||
|
{
|
||||||
|
public class DispatchListItemDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? DispatchNumber { get; set; }
|
||||||
|
public string? PONumber { get; set; }
|
||||||
|
public decimal? NTEAmount { get; set; }
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public DateTime? DispatchedAt { get; set; }
|
||||||
|
public DateTime? ScheduledDate { get; set; }
|
||||||
|
public DateTime? CompletedDate { get; set; }
|
||||||
|
public DateTime? AcknowledgedAt { get; set; }
|
||||||
|
public DateTime? VerifiedAt { get; set; }
|
||||||
|
public int? VendorId { get; set; }
|
||||||
|
public string? VendorName { get; set; }
|
||||||
|
public int? WorkOrderId { get; set; }
|
||||||
|
public string? PrimaryWOInternalNumber { get; set; }
|
||||||
|
public string? PrimaryWOTitle { get; set; }
|
||||||
|
public string? LocationName { get; set; }
|
||||||
|
public int WorkOrderCount { get; set; }
|
||||||
|
public int PendingUpliftCount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchListResult
|
||||||
|
{
|
||||||
|
public IReadOnlyList<DispatchListItemDTO> Items { get; set; } = Array.Empty<DispatchListItemDTO>();
|
||||||
|
public int Total { get; set; }
|
||||||
|
public int Page { get; set; }
|
||||||
|
public int PageSize { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchListQuery
|
||||||
|
{
|
||||||
|
public string? Search { get; set; }
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public int? VendorId { get; set; }
|
||||||
|
public DateTime? DateFrom { get; set; }
|
||||||
|
public DateTime? DateTo { get; set; }
|
||||||
|
public string? SortBy { get; set; }
|
||||||
|
public string? SortDir { get; set; }
|
||||||
|
public int Page { get; set; }
|
||||||
|
public int PageSize { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchDetailVendorDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? CompanyName { get; set; }
|
||||||
|
public string? ContactName { get; set; }
|
||||||
|
public string? Email { get; set; }
|
||||||
|
public string? Phone { get; set; }
|
||||||
|
public string? Address { get; set; }
|
||||||
|
public string? City { get; set; }
|
||||||
|
public string? State { get; set; }
|
||||||
|
public string? Zip { get; set; }
|
||||||
|
public string? TradeSpecialties { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchPrimaryLocationDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? Address1 { get; set; }
|
||||||
|
public string? City { get; set; }
|
||||||
|
public string? State { get; set; }
|
||||||
|
public string? Zip { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchLinkedWorkOrderDTO
|
||||||
|
{
|
||||||
|
public int WorkOrderId { get; set; }
|
||||||
|
public string? InternalWONumber { get; set; }
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string? Priority { get; set; }
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public string? LocationName { get; set; }
|
||||||
|
public string? Trade { get; set; }
|
||||||
|
public string? SubTrade { get; set; }
|
||||||
|
public string? Problem { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchChecklistDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? ItemText { get; set; }
|
||||||
|
public bool IsCompleted { get; set; }
|
||||||
|
public string? CompletedBy { get; set; }
|
||||||
|
public DateTime? CompletedAt { get; set; }
|
||||||
|
public int SortOrder { get; set; }
|
||||||
|
public int? WorkOrderId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchSignoffDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? SignoffType { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? Signature { get; set; }
|
||||||
|
public string? SignatureMethod { get; set; }
|
||||||
|
public DateTime? SignedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchCommentDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Commenttext { get; set; }
|
||||||
|
public string? Commenter { get; set; }
|
||||||
|
public string? CommentType { get; set; }
|
||||||
|
public string? RecordType { get; set; }
|
||||||
|
public DateTime? CreatedDate { get; set; }
|
||||||
|
public string? UserId { get; set; }
|
||||||
|
public string UserName { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchUpliftRequestDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public decimal? CurrentNTE { get; set; }
|
||||||
|
public decimal RequestedNTE { get; set; }
|
||||||
|
public decimal Delta { get; set; }
|
||||||
|
public string? VendorReason { get; set; }
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public int RequiredTier { get; set; }
|
||||||
|
public string? RequestedByVendorName { get; set; }
|
||||||
|
public DateTime? RequestedAt { get; set; }
|
||||||
|
public DateTime? DecidedAt { get; set; }
|
||||||
|
public string? DecisionNote { get; set; }
|
||||||
|
public string DecidedByName { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchAuditLogDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Action { get; set; }
|
||||||
|
public string? FieldName { get; set; }
|
||||||
|
public string? OldValue { get; set; }
|
||||||
|
public string? NewValue { get; set; }
|
||||||
|
public DateTime? CreatedAt { get; set; }
|
||||||
|
public string? UserId { get; set; }
|
||||||
|
public string UserName { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchDetailDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? DispatchNumber { get; set; }
|
||||||
|
public string? PONumber { get; set; }
|
||||||
|
public decimal? NTEAmount { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public DateTime? ScheduledDate { get; set; }
|
||||||
|
public DateTime? CompletedDate { get; set; }
|
||||||
|
public DateTime? DispatchedAt { get; set; }
|
||||||
|
public DateTime? AcknowledgedAt { get; set; }
|
||||||
|
public DateTime? VerifiedAt { get; set; }
|
||||||
|
public string? VerifiedBy { get; set; }
|
||||||
|
public bool EmailSent { get; set; }
|
||||||
|
public int? WorkOrderId { get; set; }
|
||||||
|
public DispatchDetailVendorDTO? Vendor { get; set; }
|
||||||
|
public DispatchPrimaryLocationDTO? PrimaryLocation { get; set; }
|
||||||
|
public IReadOnlyList<DispatchLinkedWorkOrderDTO> WorkOrders { get; set; } = Array.Empty<DispatchLinkedWorkOrderDTO>();
|
||||||
|
public IReadOnlyList<DispatchChecklistDTO> Checklist { get; set; } = Array.Empty<DispatchChecklistDTO>();
|
||||||
|
public IReadOnlyList<DispatchSignoffDTO> Signoffs { get; set; } = Array.Empty<DispatchSignoffDTO>();
|
||||||
|
public IReadOnlyList<DispatchCommentDTO> Comments { get; set; } = Array.Empty<DispatchCommentDTO>();
|
||||||
|
public IReadOnlyList<DispatchUpliftRequestDTO> UpliftRequests { get; set; } = Array.Empty<DispatchUpliftRequestDTO>();
|
||||||
|
public IReadOnlyList<DispatchAuditLogDTO> AuditLog { get; set; } = Array.Empty<DispatchAuditLogDTO>();
|
||||||
|
}
|
||||||
|
}
|
||||||
8
SeaHaven.DataServices/Dto/SettingsReadModels.cs
Normal file
8
SeaHaven.DataServices/Dto/SettingsReadModels.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace SeaHaven.DataServices.Dto
|
||||||
|
{
|
||||||
|
public class SettingsCategoryItemDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using Data.SeaHavenIndustries;
|
using Data.SeaHavenIndustries;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SeaHaven.DataServices.Dto;
|
||||||
using SeaHaven.DataServices.Interfaces;
|
using SeaHaven.DataServices.Interfaces;
|
||||||
|
|
||||||
namespace SeaHaven.DataServices.Implementation
|
namespace SeaHaven.DataServices.Implementation
|
||||||
|
|
@ -216,5 +217,346 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
await _context.DispatchWorkOrders.AddAsync(dispatchWorkOrder);
|
await _context.DispatchWorkOrders.AddAsync(dispatchWorkOrder);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<DispatchListResult> GetListAsync(DispatchListQuery query, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var q = _context.Dispatches
|
||||||
|
.Include(d => d.Vendor)
|
||||||
|
.Include(d => d.WorkOrder).ThenInclude(w => w!.Locations)
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(d => d.IsDeleted == null || d.IsDeleted == false);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(query.Search))
|
||||||
|
{
|
||||||
|
var s = query.Search.ToLower();
|
||||||
|
q = q.Where(d =>
|
||||||
|
(d.DispatchNumber ?? "").ToLower().Contains(s) ||
|
||||||
|
(d.PONumber ?? "").ToLower().Contains(s) ||
|
||||||
|
(d.Vendor != null && (d.Vendor.CompanyName ?? "").ToLower().Contains(s)) ||
|
||||||
|
(d.WorkOrder != null && (d.WorkOrder.InternalWONumber ?? "").ToLower().Contains(s)) ||
|
||||||
|
(d.WorkOrder != null && (d.WorkOrder.WorkerOrderTitle ?? "").ToLower().Contains(s)) ||
|
||||||
|
(d.WorkOrder != null && d.WorkOrder.Locations != null && (d.WorkOrder.Locations.Name ?? "").ToLower().Contains(s))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(query.Status))
|
||||||
|
q = q.Where(d => d.Status == query.Status);
|
||||||
|
|
||||||
|
if (query.VendorId.HasValue)
|
||||||
|
q = q.Where(d => d.VendorId == query.VendorId.Value);
|
||||||
|
|
||||||
|
if (query.DateFrom.HasValue)
|
||||||
|
q = q.Where(d => d.DispatchedAt >= query.DateFrom.Value);
|
||||||
|
|
||||||
|
if (query.DateTo.HasValue)
|
||||||
|
{
|
||||||
|
var end = query.DateTo.Value.Date.AddDays(1);
|
||||||
|
q = q.Where(d => d.DispatchedAt < end);
|
||||||
|
}
|
||||||
|
|
||||||
|
var total = await q.CountAsync(cancellationToken);
|
||||||
|
|
||||||
|
var dir = (query.SortDir ?? "desc").ToLowerInvariant() == "asc";
|
||||||
|
switch ((query.SortBy ?? "").ToLowerInvariant())
|
||||||
|
{
|
||||||
|
case "ponumber":
|
||||||
|
q = dir ? q.OrderBy(d => d.PONumber) : q.OrderByDescending(d => d.PONumber);
|
||||||
|
break;
|
||||||
|
case "dispatchnumber":
|
||||||
|
q = dir ? q.OrderBy(d => d.DispatchNumber) : q.OrderByDescending(d => d.DispatchNumber);
|
||||||
|
break;
|
||||||
|
case "vendor":
|
||||||
|
q = dir ? q.OrderBy(d => d.Vendor!.CompanyName) : q.OrderByDescending(d => d.Vendor!.CompanyName);
|
||||||
|
break;
|
||||||
|
case "nte":
|
||||||
|
case "nteamount":
|
||||||
|
q = dir ? q.OrderBy(d => d.NTEAmount) : q.OrderByDescending(d => d.NTEAmount);
|
||||||
|
break;
|
||||||
|
case "status":
|
||||||
|
q = dir ? q.OrderBy(d => d.Status) : q.OrderByDescending(d => d.Status);
|
||||||
|
break;
|
||||||
|
case "scheduleddate":
|
||||||
|
q = dir ? q.OrderBy(d => d.ScheduledDate) : q.OrderByDescending(d => d.ScheduledDate);
|
||||||
|
break;
|
||||||
|
case "completeddate":
|
||||||
|
q = dir ? q.OrderBy(d => d.CompletedDate) : q.OrderByDescending(d => d.CompletedDate);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
q = dir ? q.OrderBy(d => d.DispatchedAt) : q.OrderByDescending(d => d.DispatchedAt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows = await q
|
||||||
|
.Skip((Math.Max(query.Page, 1) - 1) * query.PageSize)
|
||||||
|
.Take(query.PageSize)
|
||||||
|
.Select(d => new DispatchListItemDTO
|
||||||
|
{
|
||||||
|
Id = d.Id,
|
||||||
|
DispatchNumber = d.DispatchNumber,
|
||||||
|
PONumber = d.PONumber,
|
||||||
|
NTEAmount = d.NTEAmount,
|
||||||
|
Status = d.Status,
|
||||||
|
DispatchedAt = d.DispatchedAt,
|
||||||
|
ScheduledDate = d.ScheduledDate,
|
||||||
|
CompletedDate = d.CompletedDate,
|
||||||
|
AcknowledgedAt = d.AcknowledgedAt,
|
||||||
|
VerifiedAt = d.VerifiedAt,
|
||||||
|
VendorId = d.VendorId,
|
||||||
|
VendorName = d.Vendor != null ? d.Vendor.CompanyName : null,
|
||||||
|
WorkOrderId = d.WorkOrderId,
|
||||||
|
PrimaryWOInternalNumber = d.WorkOrder != null ? d.WorkOrder.InternalWONumber : null,
|
||||||
|
PrimaryWOTitle = d.WorkOrder != null ? d.WorkOrder.WorkerOrderTitle : null,
|
||||||
|
LocationName = d.WorkOrder != null && d.WorkOrder.Locations != null ? d.WorkOrder.Locations.Name : null,
|
||||||
|
WorkOrderCount = _context.DispatchWorkOrders.Count(dwo => dwo.DispatchId == d.Id),
|
||||||
|
PendingUpliftCount = _context.DispatchUpliftRequests.Count(u => u.DispatchId == d.Id && u.Status == "Pending" && (u.IsDeleted == null || u.IsDeleted == false))
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return new DispatchListResult
|
||||||
|
{
|
||||||
|
Items = rows,
|
||||||
|
Total = total,
|
||||||
|
Page = query.Page,
|
||||||
|
PageSize = query.PageSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DispatchDetailDTO?> GetDetailAsync(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var dispatch = await _context.Dispatches
|
||||||
|
.Include(d => d.Vendor)
|
||||||
|
.Include(d => d.WorkOrder).ThenInclude(w => w!.Locations)
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(d => d.Id == id, cancellationToken);
|
||||||
|
if (dispatch == null) return null;
|
||||||
|
|
||||||
|
var linked = await _context.DispatchWorkOrders
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(dwo => dwo.DispatchId == id)
|
||||||
|
.Select(dwo => new DispatchLinkedWorkOrderDTO
|
||||||
|
{
|
||||||
|
WorkOrderId = dwo.WorkOrderId,
|
||||||
|
InternalWONumber = dwo.WorkOrder != null ? dwo.WorkOrder.InternalWONumber : null,
|
||||||
|
Title = dwo.WorkOrder != null ? dwo.WorkOrder.WorkerOrderTitle : null,
|
||||||
|
Priority = dwo.WorkOrder != null ? dwo.WorkOrder.Priority : null,
|
||||||
|
Status = dwo.WorkOrder != null ? dwo.WorkOrder.Status : null,
|
||||||
|
LocationName = dwo.WorkOrder != null && dwo.WorkOrder.Locations != null ? dwo.WorkOrder.Locations.Name : null,
|
||||||
|
Trade = dwo.WorkOrder != null ? dwo.WorkOrder.Trade : null,
|
||||||
|
SubTrade = dwo.WorkOrder != null ? dwo.WorkOrder.SubTrade : null,
|
||||||
|
Problem = dwo.WorkOrder != null ? dwo.WorkOrder.Problem : null
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var linkedWOs = linked;
|
||||||
|
if (linkedWOs.Count == 0 && dispatch.WorkOrderId.HasValue)
|
||||||
|
{
|
||||||
|
var wo = await _context.workOrders
|
||||||
|
.Include(w => w.Locations)
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(w => w.Id == dispatch.WorkOrderId.Value, cancellationToken);
|
||||||
|
if (wo != null)
|
||||||
|
{
|
||||||
|
linkedWOs = new List<DispatchLinkedWorkOrderDTO>
|
||||||
|
{
|
||||||
|
new DispatchLinkedWorkOrderDTO
|
||||||
|
{
|
||||||
|
WorkOrderId = wo.Id,
|
||||||
|
InternalWONumber = wo.InternalWONumber,
|
||||||
|
Title = wo.WorkerOrderTitle,
|
||||||
|
Priority = wo.Priority,
|
||||||
|
Status = wo.Status,
|
||||||
|
LocationName = wo.Locations != null ? wo.Locations.Name : null,
|
||||||
|
Trade = wo.Trade,
|
||||||
|
SubTrade = wo.SubTrade,
|
||||||
|
Problem = wo.Problem
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var checklist = await _context.DispatchChecklistItems
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(c => c.DispatchId == id && (c.IsDeleted == null || c.IsDeleted == false))
|
||||||
|
.OrderBy(c => c.SortOrder)
|
||||||
|
.Select(c => new DispatchChecklistDTO
|
||||||
|
{
|
||||||
|
Id = c.Id,
|
||||||
|
ItemText = c.ItemText,
|
||||||
|
IsCompleted = c.IsCompleted,
|
||||||
|
CompletedBy = c.CompletedBy,
|
||||||
|
CompletedAt = c.CompletedAt,
|
||||||
|
SortOrder = c.SortOrder,
|
||||||
|
WorkOrderId = c.WorkOrderId
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var signoffs = await _context.DispatchSignoffs
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(s => s.DispatchId == id && (s.IsDeleted == null || s.IsDeleted == false))
|
||||||
|
.Select(s => new DispatchSignoffDTO
|
||||||
|
{
|
||||||
|
Id = s.Id,
|
||||||
|
SignoffType = s.SignoffType,
|
||||||
|
Name = s.Name,
|
||||||
|
Signature = s.Signature,
|
||||||
|
SignatureMethod = s.SignatureMethod,
|
||||||
|
SignedAt = s.SignedAt
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var rawComments = await (from c in _context.Comments
|
||||||
|
where c.DispatchId == id && (c.IsDeleted == null || c.IsDeleted == false)
|
||||||
|
join u in _context.Users on c.UserId equals u.Id into users
|
||||||
|
from u in users.DefaultIfEmpty()
|
||||||
|
orderby c.CreatedDate
|
||||||
|
select new
|
||||||
|
{
|
||||||
|
c.Id,
|
||||||
|
c.Commenttext,
|
||||||
|
c.Commenter,
|
||||||
|
c.CommentType,
|
||||||
|
c.RecordType,
|
||||||
|
c.CreatedDate,
|
||||||
|
c.UserId,
|
||||||
|
UserFirstName = u != null ? u.FirstName : null,
|
||||||
|
UserLastName = u != null ? u.LastName : null
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var comments = rawComments.Select(c => new DispatchCommentDTO
|
||||||
|
{
|
||||||
|
Id = c.Id,
|
||||||
|
Commenttext = c.Commenttext,
|
||||||
|
Commenter = c.Commenter,
|
||||||
|
CommentType = c.CommentType,
|
||||||
|
RecordType = c.RecordType,
|
||||||
|
CreatedDate = c.CreatedDate,
|
||||||
|
UserId = c.UserId,
|
||||||
|
UserName = string.Join(" ", new[] { c.UserFirstName, c.UserLastName }
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim()
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
var rawUplifts = await (from u in _context.DispatchUpliftRequests
|
||||||
|
where u.DispatchId == id && (u.IsDeleted == null || u.IsDeleted == false)
|
||||||
|
join dec in _context.Users on u.DecidedByUserId equals dec.Id into decs
|
||||||
|
from dec in decs.DefaultIfEmpty()
|
||||||
|
orderby u.CreatedDate descending
|
||||||
|
select new
|
||||||
|
{
|
||||||
|
u.Id,
|
||||||
|
u.CurrentNTE,
|
||||||
|
u.RequestedNTE,
|
||||||
|
u.VendorReason,
|
||||||
|
u.Status,
|
||||||
|
u.RequiredTier,
|
||||||
|
u.RequestedByVendorName,
|
||||||
|
RequestedAt = u.CreatedDate,
|
||||||
|
u.DecidedAt,
|
||||||
|
u.DecisionNote,
|
||||||
|
DecidedByFirstName = dec != null ? dec.FirstName : null,
|
||||||
|
DecidedByLastName = dec != null ? dec.LastName : null
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var upliftRequests = rawUplifts.Select(u => new DispatchUpliftRequestDTO
|
||||||
|
{
|
||||||
|
Id = u.Id,
|
||||||
|
CurrentNTE = u.CurrentNTE,
|
||||||
|
RequestedNTE = u.RequestedNTE,
|
||||||
|
Delta = u.RequestedNTE - (u.CurrentNTE ?? 0m),
|
||||||
|
VendorReason = u.VendorReason,
|
||||||
|
Status = u.Status,
|
||||||
|
RequiredTier = u.RequiredTier,
|
||||||
|
RequestedByVendorName = u.RequestedByVendorName,
|
||||||
|
RequestedAt = u.RequestedAt,
|
||||||
|
DecidedAt = u.DecidedAt,
|
||||||
|
DecisionNote = u.DecisionNote,
|
||||||
|
DecidedByName = string.Join(" ", new[] { u.DecidedByFirstName, u.DecidedByLastName }
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim()
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
var dispatchNumber = dispatch.DispatchNumber ?? "";
|
||||||
|
var workOrderIds = linkedWOs.Select(l => l.WorkOrderId).Where(wid => wid > 0).ToList();
|
||||||
|
|
||||||
|
var rawAudit = await (from a in _context.WorkOrderAuditLogs
|
||||||
|
where workOrderIds.Contains(a.WorkOrderId)
|
||||||
|
&& a.FieldName != null
|
||||||
|
&& a.FieldName.Contains(dispatchNumber)
|
||||||
|
join u in _context.Users on a.UserId equals u.Id into users
|
||||||
|
from u in users.DefaultIfEmpty()
|
||||||
|
orderby a.CreatedAt descending
|
||||||
|
select new
|
||||||
|
{
|
||||||
|
a.Id,
|
||||||
|
a.Action,
|
||||||
|
a.FieldName,
|
||||||
|
a.OldValue,
|
||||||
|
a.NewValue,
|
||||||
|
a.CreatedAt,
|
||||||
|
a.UserId,
|
||||||
|
UserFirstName = u != null ? u.FirstName : null,
|
||||||
|
UserLastName = u != null ? u.LastName : null
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
var auditLog = rawAudit.Select(a => new DispatchAuditLogDTO
|
||||||
|
{
|
||||||
|
Id = a.Id,
|
||||||
|
Action = a.Action,
|
||||||
|
FieldName = a.FieldName,
|
||||||
|
OldValue = a.OldValue,
|
||||||
|
NewValue = a.NewValue,
|
||||||
|
CreatedAt = a.CreatedAt,
|
||||||
|
UserId = a.UserId,
|
||||||
|
UserName = string.Join(" ", new[] { a.UserFirstName, a.UserLastName }
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s))).Trim()
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return new DispatchDetailDTO
|
||||||
|
{
|
||||||
|
Id = dispatch.Id,
|
||||||
|
DispatchNumber = dispatch.DispatchNumber,
|
||||||
|
PONumber = dispatch.PONumber,
|
||||||
|
NTEAmount = dispatch.NTEAmount,
|
||||||
|
Description = dispatch.Description,
|
||||||
|
Status = dispatch.Status,
|
||||||
|
ScheduledDate = dispatch.ScheduledDate,
|
||||||
|
CompletedDate = dispatch.CompletedDate,
|
||||||
|
DispatchedAt = dispatch.DispatchedAt,
|
||||||
|
AcknowledgedAt = dispatch.AcknowledgedAt,
|
||||||
|
VerifiedAt = dispatch.VerifiedAt,
|
||||||
|
VerifiedBy = dispatch.VerifiedBy,
|
||||||
|
EmailSent = dispatch.EmailSent,
|
||||||
|
WorkOrderId = dispatch.WorkOrderId,
|
||||||
|
Vendor = dispatch.Vendor == null ? null : new DispatchDetailVendorDTO
|
||||||
|
{
|
||||||
|
Id = dispatch.Vendor.Id,
|
||||||
|
CompanyName = dispatch.Vendor.CompanyName,
|
||||||
|
ContactName = dispatch.Vendor.ContactName,
|
||||||
|
Email = dispatch.Vendor.Email,
|
||||||
|
Phone = dispatch.Vendor.Phone,
|
||||||
|
Address = dispatch.Vendor.Address,
|
||||||
|
City = dispatch.Vendor.City,
|
||||||
|
State = dispatch.Vendor.State,
|
||||||
|
Zip = dispatch.Vendor.Zip,
|
||||||
|
TradeSpecialties = dispatch.Vendor.TradeSpecialties
|
||||||
|
},
|
||||||
|
PrimaryLocation = dispatch.WorkOrder?.Locations == null ? null : new DispatchPrimaryLocationDTO
|
||||||
|
{
|
||||||
|
Id = dispatch.WorkOrder.Locations.Id,
|
||||||
|
Name = dispatch.WorkOrder.Locations.Name,
|
||||||
|
Address1 = dispatch.WorkOrder.Locations.Address1,
|
||||||
|
City = dispatch.WorkOrder.Locations.City,
|
||||||
|
State = dispatch.WorkOrder.Locations.State,
|
||||||
|
Zip = dispatch.WorkOrder.Locations.Zip
|
||||||
|
},
|
||||||
|
WorkOrders = linkedWOs,
|
||||||
|
Checklist = checklist,
|
||||||
|
Signoffs = signoffs,
|
||||||
|
Comments = comments,
|
||||||
|
UpliftRequests = upliftRequests,
|
||||||
|
AuditLog = auditLog
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -120,5 +120,138 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
{
|
{
|
||||||
return await _context.FollowUps.CountAsync();
|
return await _context.FollowUps.CountAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<FollowUps>> GetListPagedAsync(
|
||||||
|
int page,
|
||||||
|
int pageSize,
|
||||||
|
string? search,
|
||||||
|
string? status,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
search ??= "";
|
||||||
|
status = string.IsNullOrWhiteSpace(status) ? "Open" : status;
|
||||||
|
|
||||||
|
var query = _context.FollowUps
|
||||||
|
.Include(x => x.Employee)
|
||||||
|
.Include(x => x.Account)
|
||||||
|
.Include(x => x.Location)
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(x => x.Status == status);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
|
{
|
||||||
|
var s = search.ToLower();
|
||||||
|
query = query.Where(x =>
|
||||||
|
(x.Reason ?? "").ToLower().Contains(s) ||
|
||||||
|
(x.Account != null && (x.Account.Name ?? "").ToLower().Contains(s)) ||
|
||||||
|
(x.Employee != null &&
|
||||||
|
(((x.Employee.FirstName ?? "") + " " + (x.Employee.LastName ?? ""))
|
||||||
|
.ToLower().Contains(s))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query
|
||||||
|
.OrderByDescending(f => f.Id)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetListTotalAsync(string? search, string? status, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
search ??= "";
|
||||||
|
status = string.IsNullOrWhiteSpace(status) ? "Open" : status;
|
||||||
|
|
||||||
|
var query = _context.FollowUps
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(x => x.Status == status);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
|
{
|
||||||
|
var s = search.ToLower();
|
||||||
|
query = query.Where(x =>
|
||||||
|
(x.Reason ?? "").ToLower().Contains(s) ||
|
||||||
|
(x.Account != null && (x.Account.Name ?? "").ToLower().Contains(s)) ||
|
||||||
|
(x.Employee != null &&
|
||||||
|
(((x.Employee.FirstName ?? "") + " " + (x.Employee.LastName ?? ""))
|
||||||
|
.ToLower().Contains(s))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query.CountAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FollowUps?> GetDetailByIdAsync(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return await _context.FollowUps
|
||||||
|
.Include(x => x.Employee)
|
||||||
|
.Include(x => x.Account)
|
||||||
|
.Include(x => x.Location)
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(f => f.Id == id, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FollowUps?> GetByIdForUpdateAsync(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return await _context.FollowUps.FindAsync(new object[] { id }, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetUserNameAsync(string? userId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(userId))
|
||||||
|
return "";
|
||||||
|
|
||||||
|
var u = await _context.Users
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(x => x.Id == userId)
|
||||||
|
.Select(x => new { x.FirstName, x.LastName, x.UserName })
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (u == null)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
var n = ((u.FirstName ?? "") + " " + (u.LastName ?? "")).Trim();
|
||||||
|
return string.IsNullOrWhiteSpace(n) ? (u.UserName ?? "") : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FollowUps> AddAsync(FollowUps followUp, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
followUp.CreatedDate = DateTime.Now;
|
||||||
|
await _context.FollowUps.AddAsync(followUp, cancellationToken);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
return followUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAsync(FollowUps followUp, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
followUp.LastModificationTime = DateTime.Now;
|
||||||
|
_context.FollowUps.Update(followUp);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SetStatusAsync(int id, string status, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _context.FollowUps.FindAsync(new object[] { id }, cancellationToken);
|
||||||
|
if (entity == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
entity.Status = status;
|
||||||
|
entity.LastModificationTime = DateTime.Now;
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> RemoveByIdAsync(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _context.FollowUps.FindAsync(new object[] { id }, cancellationToken);
|
||||||
|
if (entity == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_context.FollowUps.Remove(entity);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,69 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
return await _context.Locations.CountAsync();
|
return await _context.Locations.CountAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<(List<Locations> Items, int TotalCount)> GetListPagedAsync(
|
||||||
|
int page,
|
||||||
|
int pageSize,
|
||||||
|
string? search,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var query = _context.Locations.AsNoTracking();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
|
{
|
||||||
|
query = query.Where(l =>
|
||||||
|
l.Name!.Contains(search) ||
|
||||||
|
l.City!.Contains(search));
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalCount = await query.CountAsync(cancellationToken);
|
||||||
|
var items = await query
|
||||||
|
.OrderBy(l => l.Name)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return (items, totalCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Locations?> GetDetailByIdAsync(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return await _context.Locations
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(l => l.Id == id, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Locations?> GetByIdForUpdateAsync(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return await _context.Locations.FindAsync(new object[] { id }, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Locations> AddAsync(Locations location, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
location.CreatedDate = DateTime.Now;
|
||||||
|
await _context.Locations.AddAsync(location, cancellationToken);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAsync(Locations location, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
location.LastModificationTime = DateTime.Now;
|
||||||
|
_context.Locations.Update(location);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteByIdAsync(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var entity = await _context.Locations.FindAsync(new object[] { id }, cancellationToken);
|
||||||
|
if (entity == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_context.Locations.Remove(entity);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<(IEnumerable<object> Items, int TotalCount)> GetAddressbookPagedAsync(
|
public async Task<(IEnumerable<object> Items, int TotalCount)> GetAddressbookPagedAsync(
|
||||||
int page,
|
int page,
|
||||||
int pageSize,
|
int pageSize,
|
||||||
|
|
|
||||||
125
SeaHaven.DataServices/Implementation/SettingsDataService.cs
Normal file
125
SeaHaven.DataServices/Implementation/SettingsDataService.cs
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
using Data.SeaHavenIndustries;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SeaHaven.DataServices.Dto;
|
||||||
|
using SeaHaven.DataServices.Interfaces;
|
||||||
|
|
||||||
|
namespace SeaHaven.DataServices.Implementation
|
||||||
|
{
|
||||||
|
public class SettingsDataService : ISettingsDataService
|
||||||
|
{
|
||||||
|
private readonly ApplicationDbContext _context;
|
||||||
|
|
||||||
|
public SettingsDataService(ApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddCategoryAsync(string name, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var category = new Category { Name = name };
|
||||||
|
await _context.Categories.AddAsync(category, cancellationToken);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UpdateCategoryAsync(int id, string name, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var exist = await _context.Categories
|
||||||
|
.Where(w => w.Id == id)
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
if (exist == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
exist.Name = name;
|
||||||
|
_context.Categories.Update(exist);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteCategoryCascadeAsync(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var exist = await _context.Categories
|
||||||
|
.Where(w => w.Id == id)
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
if (exist == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var templates = await _context.Templates.Where(w => w.CategoryId == id).ToListAsync(cancellationToken);
|
||||||
|
if (templates != null)
|
||||||
|
{
|
||||||
|
_context.RemoveRange(templates);
|
||||||
|
}
|
||||||
|
|
||||||
|
var workOrderCategories = await _context.workOrderCategories.Where(w => w.CategoryId == id).ToListAsync(cancellationToken);
|
||||||
|
if (workOrderCategories != null)
|
||||||
|
{
|
||||||
|
_context.RemoveRange(workOrderCategories);
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.Remove(exist);
|
||||||
|
await _context.SaveChangesAsync(cancellationToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(IReadOnlyList<SettingsCategoryItemDTO> Items, int Total)> GetCategoryListPagedAsync(
|
||||||
|
int page,
|
||||||
|
int pageSize,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var totalCount = await _context.Categories.CountAsync(cancellationToken);
|
||||||
|
var data = await _context.Categories
|
||||||
|
.AsNoTracking()
|
||||||
|
.Select(s => new SettingsCategoryItemDTO
|
||||||
|
{
|
||||||
|
Id = s.Id,
|
||||||
|
Name = s.Name
|
||||||
|
})
|
||||||
|
.OrderByDescending(d => d.Id)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return (data, totalCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(IReadOnlyList<WorkOrder> Items, int Total)> GetTemplatesPagedAsync(
|
||||||
|
int page,
|
||||||
|
int pageSize,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var totalCount = await _context.workOrders.CountAsync(w => w.istemplate == true, cancellationToken);
|
||||||
|
var data = await _context.workOrders
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(w => w.istemplate == true)
|
||||||
|
.OrderByDescending(d => d.Id)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return (data, totalCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(IReadOnlyList<ApplicationUser> Users, int Total)> GetUsersPagedAsync(
|
||||||
|
string? search,
|
||||||
|
int page,
|
||||||
|
int pageSize,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var totalCount = await _context.Users.CountAsync(cancellationToken);
|
||||||
|
var users = await _context.Users
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(w => (w.FirstName ?? "").ToLower().Contains((search ?? "").ToLower()))
|
||||||
|
.OrderByDescending(d => d.Id)
|
||||||
|
.Skip((page - 1) * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return (users, totalCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<string>> GetRoleNamesAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var roles = await _context.Roles.AsNoTracking().ToListAsync(cancellationToken);
|
||||||
|
return roles.Select(r => r.Name!).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Data.SeaHavenIndustries;
|
using Data.SeaHavenIndustries;
|
||||||
|
using SeaHaven.DataServices.Dto;
|
||||||
|
|
||||||
namespace SeaHaven.DataServices.Interfaces
|
namespace SeaHaven.DataServices.Interfaces
|
||||||
{
|
{
|
||||||
|
|
@ -28,5 +29,8 @@ namespace SeaHaven.DataServices.Interfaces
|
||||||
Task<bool> HasSignoffTypeAsync(int dispatchId, string signoffType);
|
Task<bool> HasSignoffTypeAsync(int dispatchId, string signoffType);
|
||||||
Task<IEnumerable<DispatchWorkOrder>> GetDispatchWorkOrdersAsync(int dispatchId);
|
Task<IEnumerable<DispatchWorkOrder>> GetDispatchWorkOrdersAsync(int dispatchId);
|
||||||
Task AddDispatchWorkOrderAsync(DispatchWorkOrder dispatchWorkOrder);
|
Task AddDispatchWorkOrderAsync(DispatchWorkOrder dispatchWorkOrder);
|
||||||
|
|
||||||
|
Task<DispatchListResult> GetListAsync(DispatchListQuery query, CancellationToken cancellationToken);
|
||||||
|
Task<DispatchDetailDTO?> GetDetailAsync(int id, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,15 @@ namespace SeaHaven.DataServices.Interfaces
|
||||||
Task DeleteAsync(int id);
|
Task DeleteAsync(int id);
|
||||||
Task<bool> ExistsAsync(int id);
|
Task<bool> ExistsAsync(int id);
|
||||||
Task<int> CountAsync();
|
Task<int> CountAsync();
|
||||||
|
|
||||||
|
Task<List<FollowUps>> GetListPagedAsync(int page, int pageSize, string? search, string? status, CancellationToken cancellationToken);
|
||||||
|
Task<int> GetListTotalAsync(string? search, string? status, CancellationToken cancellationToken);
|
||||||
|
Task<FollowUps?> GetDetailByIdAsync(int id, CancellationToken cancellationToken);
|
||||||
|
Task<FollowUps?> GetByIdForUpdateAsync(int id, CancellationToken cancellationToken);
|
||||||
|
Task<string> GetUserNameAsync(string? userId, CancellationToken cancellationToken);
|
||||||
|
Task<FollowUps> AddAsync(FollowUps followUp, CancellationToken cancellationToken);
|
||||||
|
Task UpdateAsync(FollowUps followUp, CancellationToken cancellationToken);
|
||||||
|
Task<bool> SetStatusAsync(int id, string status, CancellationToken cancellationToken);
|
||||||
|
Task<bool> RemoveByIdAsync(int id, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,12 @@ namespace SeaHaven.DataServices.Interfaces
|
||||||
Task DeleteAsync(int id);
|
Task DeleteAsync(int id);
|
||||||
Task<bool> ExistsAsync(int id);
|
Task<bool> ExistsAsync(int id);
|
||||||
Task<int> CountAsync();
|
Task<int> CountAsync();
|
||||||
|
|
||||||
|
Task<(List<Locations> Items, int TotalCount)> GetListPagedAsync(int page, int pageSize, string? search, CancellationToken cancellationToken);
|
||||||
|
Task<Locations?> GetDetailByIdAsync(int id, CancellationToken cancellationToken);
|
||||||
|
Task<Locations?> GetByIdForUpdateAsync(int id, CancellationToken cancellationToken);
|
||||||
|
Task<Locations> AddAsync(Locations location, CancellationToken cancellationToken);
|
||||||
|
Task UpdateAsync(Locations location, CancellationToken cancellationToken);
|
||||||
|
Task<bool> DeleteByIdAsync(int id, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
SeaHaven.DataServices/Interfaces/ISettingsDataService.cs
Normal file
16
SeaHaven.DataServices/Interfaces/ISettingsDataService.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
using Data.SeaHavenIndustries;
|
||||||
|
using SeaHaven.DataServices.Dto;
|
||||||
|
|
||||||
|
namespace SeaHaven.DataServices.Interfaces
|
||||||
|
{
|
||||||
|
public interface ISettingsDataService
|
||||||
|
{
|
||||||
|
Task AddCategoryAsync(string name, CancellationToken cancellationToken);
|
||||||
|
Task<bool> UpdateCategoryAsync(int id, string name, CancellationToken cancellationToken);
|
||||||
|
Task<bool> DeleteCategoryCascadeAsync(int id, CancellationToken cancellationToken);
|
||||||
|
Task<(IReadOnlyList<SettingsCategoryItemDTO> Items, int Total)> GetCategoryListPagedAsync(int page, int pageSize, CancellationToken cancellationToken);
|
||||||
|
Task<(IReadOnlyList<WorkOrder> Items, int Total)> GetTemplatesPagedAsync(int page, int pageSize, CancellationToken cancellationToken);
|
||||||
|
Task<(IReadOnlyList<ApplicationUser> Users, int Total)> GetUsersPagedAsync(string? search, int page, int pageSize, CancellationToken cancellationToken);
|
||||||
|
Task<IReadOnlyList<string>> GetRoleNamesAsync(CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,4 +27,158 @@ namespace SeaHaven.Services.DTOs
|
||||||
public DateTime? CompletedDate { get; set; }
|
public DateTime? CompletedDate { get; set; }
|
||||||
public string? Status { get; set; }
|
public string? Status { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class DispatchListEntryDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? DispatchNumber { get; set; }
|
||||||
|
public string? PONumber { get; set; }
|
||||||
|
public decimal? NTEAmount { get; set; }
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public DateTime? DispatchedAt { get; set; }
|
||||||
|
public DateTime? ScheduledDate { get; set; }
|
||||||
|
public DateTime? CompletedDate { get; set; }
|
||||||
|
public DateTime? AcknowledgedAt { get; set; }
|
||||||
|
public DateTime? VerifiedAt { get; set; }
|
||||||
|
public int? VendorId { get; set; }
|
||||||
|
public string? VendorName { get; set; }
|
||||||
|
public int? WorkOrderId { get; set; }
|
||||||
|
public string? PrimaryWOInternalNumber { get; set; }
|
||||||
|
public string? PrimaryWOTitle { get; set; }
|
||||||
|
public string? LocationName { get; set; }
|
||||||
|
public int WorkOrderCount { get; set; }
|
||||||
|
public int PendingUpliftCount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchListResultDTO
|
||||||
|
{
|
||||||
|
public IReadOnlyList<DispatchListEntryDTO> Items { get; set; } = Array.Empty<DispatchListEntryDTO>();
|
||||||
|
public int Total { get; set; }
|
||||||
|
public int Page { get; set; }
|
||||||
|
public int PageSize { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchDetailVendorResultDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? CompanyName { get; set; }
|
||||||
|
public string? ContactName { get; set; }
|
||||||
|
public string? Email { get; set; }
|
||||||
|
public string? Phone { get; set; }
|
||||||
|
public string? Address { get; set; }
|
||||||
|
public string? City { get; set; }
|
||||||
|
public string? State { get; set; }
|
||||||
|
public string? Zip { get; set; }
|
||||||
|
public string? TradeSpecialties { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchDetailLocationResultDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? Address1 { get; set; }
|
||||||
|
public string? City { get; set; }
|
||||||
|
public string? State { get; set; }
|
||||||
|
public string? Zip { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchDetailLinkedWorkOrderDTO
|
||||||
|
{
|
||||||
|
public int WorkOrderId { get; set; }
|
||||||
|
public string? InternalWONumber { get; set; }
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string? Priority { get; set; }
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public string? LocationName { get; set; }
|
||||||
|
public string? Trade { get; set; }
|
||||||
|
public string? SubTrade { get; set; }
|
||||||
|
public string? Problem { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchDetailChecklistDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? ItemText { get; set; }
|
||||||
|
public bool IsCompleted { get; set; }
|
||||||
|
public string? CompletedBy { get; set; }
|
||||||
|
public DateTime? CompletedAt { get; set; }
|
||||||
|
public int SortOrder { get; set; }
|
||||||
|
public int? WorkOrderId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchDetailSignoffDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? SignoffType { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? Signature { get; set; }
|
||||||
|
public string? SignatureMethod { get; set; }
|
||||||
|
public DateTime? SignedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchDetailCommentDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Commenttext { get; set; }
|
||||||
|
public string? Commenter { get; set; }
|
||||||
|
public string? CommentType { get; set; }
|
||||||
|
public string? RecordType { get; set; }
|
||||||
|
public DateTime? CreatedDate { get; set; }
|
||||||
|
public string? UserId { get; set; }
|
||||||
|
public string UserName { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchDetailUpliftDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public decimal? CurrentNTE { get; set; }
|
||||||
|
public decimal RequestedNTE { get; set; }
|
||||||
|
public decimal Delta { get; set; }
|
||||||
|
public string? VendorReason { get; set; }
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public int RequiredTier { get; set; }
|
||||||
|
public string? RequestedByVendorName { get; set; }
|
||||||
|
public DateTime? RequestedAt { get; set; }
|
||||||
|
public DateTime? DecidedAt { get; set; }
|
||||||
|
public string? DecisionNote { get; set; }
|
||||||
|
public string DecidedByName { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchDetailAuditLogDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Action { get; set; }
|
||||||
|
public string? FieldName { get; set; }
|
||||||
|
public string? OldValue { get; set; }
|
||||||
|
public string? NewValue { get; set; }
|
||||||
|
public DateTime? CreatedAt { get; set; }
|
||||||
|
public string? UserId { get; set; }
|
||||||
|
public string UserName { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DispatchDetailResultDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? DispatchNumber { get; set; }
|
||||||
|
public string? PONumber { get; set; }
|
||||||
|
public decimal? NTEAmount { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public DateTime? ScheduledDate { get; set; }
|
||||||
|
public DateTime? CompletedDate { get; set; }
|
||||||
|
public DateTime? DispatchedAt { get; set; }
|
||||||
|
public DateTime? AcknowledgedAt { get; set; }
|
||||||
|
public DateTime? VerifiedAt { get; set; }
|
||||||
|
public string? VerifiedBy { get; set; }
|
||||||
|
public bool EmailSent { get; set; }
|
||||||
|
public int? WorkOrderId { get; set; }
|
||||||
|
public DispatchDetailVendorResultDTO? Vendor { get; set; }
|
||||||
|
public DispatchDetailLocationResultDTO? PrimaryLocation { get; set; }
|
||||||
|
public IReadOnlyList<DispatchDetailLinkedWorkOrderDTO> WorkOrders { get; set; } = Array.Empty<DispatchDetailLinkedWorkOrderDTO>();
|
||||||
|
public IReadOnlyList<DispatchDetailChecklistDTO> Checklist { get; set; } = Array.Empty<DispatchDetailChecklistDTO>();
|
||||||
|
public IReadOnlyList<DispatchDetailSignoffDTO> Signoffs { get; set; } = Array.Empty<DispatchDetailSignoffDTO>();
|
||||||
|
public IReadOnlyList<DispatchDetailCommentDTO> Comments { get; set; } = Array.Empty<DispatchDetailCommentDTO>();
|
||||||
|
public IReadOnlyList<DispatchDetailUpliftDTO> UpliftRequests { get; set; } = Array.Empty<DispatchDetailUpliftDTO>();
|
||||||
|
public IReadOnlyList<DispatchDetailAuditLogDTO> AuditLog { get; set; } = Array.Empty<DispatchDetailAuditLogDTO>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,4 +40,69 @@ namespace SeaHaven.Services.DTOs
|
||||||
public int? AccountId { get; set; }
|
public int? AccountId { get; set; }
|
||||||
public int? LocationId { get; set; }
|
public int? LocationId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class FollowUpCreateRequestDTO
|
||||||
|
{
|
||||||
|
public string? Reason { get; set; }
|
||||||
|
public DateTime ScheduleDate { get; set; }
|
||||||
|
public TimeSpan ScheduleStartTime { get; set; }
|
||||||
|
public int? EmployeeId { get; set; }
|
||||||
|
public int? WorkOrderId { get; set; }
|
||||||
|
public int? AccountId { get; set; }
|
||||||
|
public int? LocationId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FollowUpUpdateRequestDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Reason { get; set; }
|
||||||
|
public DateTime ScheduleDate { get; set; }
|
||||||
|
public TimeSpan ScheduleStartTime { get; set; }
|
||||||
|
public int? EmployeeId { get; set; }
|
||||||
|
public int? WorkOrderId { get; set; }
|
||||||
|
public int? AccountId { get; set; }
|
||||||
|
public int? LocationId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FollowUpListRowDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Reason { get; set; }
|
||||||
|
public DateTime ScheduleDate { get; set; }
|
||||||
|
public TimeSpan ScheduleStartTime { get; set; }
|
||||||
|
public int? EmployeeId { get; set; }
|
||||||
|
public int? WorkOrderId { get; set; }
|
||||||
|
public int? AccountId { get; set; }
|
||||||
|
public int? LocationId { get; set; }
|
||||||
|
public string AccountName { get; set; } = "";
|
||||||
|
public string AssignedTo { get; set; } = "";
|
||||||
|
public string LocationName { get; set; } = "";
|
||||||
|
public string WorkOrderNumber { get; set; } = "";
|
||||||
|
public string InternalWONumber { get; set; } = "";
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public DateTime? CreatedDate { get; set; }
|
||||||
|
public string? CreatedById { get; set; }
|
||||||
|
public string CreatedByName { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FollowUpDetailDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Reason { get; set; }
|
||||||
|
public DateTime ScheduleDate { get; set; }
|
||||||
|
public TimeSpan ScheduleStartTime { get; set; }
|
||||||
|
public int? EmployeeId { get; set; }
|
||||||
|
public int? WorkOrderId { get; set; }
|
||||||
|
public int? AccountId { get; set; }
|
||||||
|
public int? LocationId { get; set; }
|
||||||
|
public string AccountName { get; set; } = "";
|
||||||
|
public string AssignedTo { get; set; } = "";
|
||||||
|
public string LocationName { get; set; } = "";
|
||||||
|
public string WorkOrderNumber { get; set; } = "";
|
||||||
|
public string InternalWONumber { get; set; } = "";
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public DateTime? CreatedDate { get; set; }
|
||||||
|
public string? CreatedById { get; set; }
|
||||||
|
public string CreatedByName { get; set; } = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,4 +36,30 @@ namespace SeaHaven.Services.DTOs
|
||||||
public string? Zipcode { get; set; }
|
public string? Zipcode { get; set; }
|
||||||
public int? AccountId { get; set; }
|
public int? AccountId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class LocationCreateRequestDTO
|
||||||
|
{
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string? Address { get; set; }
|
||||||
|
public string? City { get; set; }
|
||||||
|
public string? State { get; set; }
|
||||||
|
public string? ZipCode { get; set; }
|
||||||
|
public string? Phone { get; set; }
|
||||||
|
public string? ContactEmail { get; set; }
|
||||||
|
public string? Status { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LocationUpdateRequestDTO
|
||||||
|
{
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string? Address { get; set; }
|
||||||
|
public string? City { get; set; }
|
||||||
|
public string? State { get; set; }
|
||||||
|
public string? ZipCode { get; set; }
|
||||||
|
public string? Phone { get; set; }
|
||||||
|
public string? ContactEmail { get; set; }
|
||||||
|
public string? Status { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
98
SeaHaven.Services/DTOs/SettingsDTOs.cs
Normal file
98
SeaHaven.Services/DTOs/SettingsDTOs.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
using Data.SeaHavenIndustries.Enums;
|
||||||
|
|
||||||
|
namespace SeaHaven.Services.DTOs
|
||||||
|
{
|
||||||
|
public class RoleDTO
|
||||||
|
{
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SettingsUserDTO
|
||||||
|
{
|
||||||
|
public string? Id { get; set; }
|
||||||
|
public string? UserName { get; set; }
|
||||||
|
public string? Email { get; set; }
|
||||||
|
public string? RoleName { get; set; }
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public string? Date { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SettingsCategoryDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SettingsTemplateDTO
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public bool? IsDeleted { get; set; }
|
||||||
|
public string? createdby { get; set; }
|
||||||
|
public string? DeleterUserId { get; set; }
|
||||||
|
public DateTime? DeletionTime { get; set; }
|
||||||
|
public DateTime? CreatedDate { get; set; }
|
||||||
|
public DateTime? LastModificationTime { get; set; }
|
||||||
|
public int? LastModifierUserId { get; set; }
|
||||||
|
|
||||||
|
public string? InternalWONumber { get; set; }
|
||||||
|
public string? ExternalWorkOrderId { get; set; }
|
||||||
|
public string? WorkerOrderNumber { get; set; }
|
||||||
|
public string? WorkerOrderTitle { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public string? Customer { get; set; }
|
||||||
|
public string? SiteCode { get; set; }
|
||||||
|
public string? Building { get; set; }
|
||||||
|
public string? Severity { get; set; }
|
||||||
|
public DateTime? DueDate { get; set; }
|
||||||
|
public string? AssignTo { get; set; }
|
||||||
|
public int? LocationId { get; set; }
|
||||||
|
public LifecycleStatus? LifecycleStatus { get; set; }
|
||||||
|
public string? LegacyStatus { get; set; }
|
||||||
|
public WorkOrderType? WorkOrderType { get; set; }
|
||||||
|
public string? Service { get; set; }
|
||||||
|
public int? PrimaryDispatchId { get; set; }
|
||||||
|
|
||||||
|
public DateTime? ScheduledDate { get; set; }
|
||||||
|
public DateTime? ScheduledStart { get; set; }
|
||||||
|
public DateTime? ScheduledEnd { get; set; }
|
||||||
|
public DateOnly? TargetWeek { get; set; }
|
||||||
|
public bool? ScheduleWeekOnly { get; set; }
|
||||||
|
|
||||||
|
public DateOnly? OriginalWeek { get; set; }
|
||||||
|
public DateOnly? OriginalDate { get; set; }
|
||||||
|
|
||||||
|
public int RescheduleCount { get; set; }
|
||||||
|
public int CarriedOver { get; set; }
|
||||||
|
public OperationalFlags OperationalFlags { get; set; }
|
||||||
|
|
||||||
|
public DocStatus? DocStatus { get; set; }
|
||||||
|
|
||||||
|
public string? AvettaTask { get; set; }
|
||||||
|
public DateOnly? AssignDate { get; set; }
|
||||||
|
|
||||||
|
public DateTime? DateReported { get; set; }
|
||||||
|
public string? SourceEmailS3Key { get; set; }
|
||||||
|
public string? Problem { get; set; }
|
||||||
|
public string? Trade { get; set; }
|
||||||
|
public string? SubTrade { get; set; }
|
||||||
|
public decimal? VendorNTE { get; set; }
|
||||||
|
public DateTime? CompletedDate { get; set; }
|
||||||
|
public string? Source { get; set; }
|
||||||
|
public bool? istemplate { get; set; }
|
||||||
|
public string? PO { get; set; }
|
||||||
|
public string? TT { get; set; }
|
||||||
|
public string? Priority { get; set; }
|
||||||
|
public string? Status { get; set; }
|
||||||
|
public string? Attachments { get; set; }
|
||||||
|
public string? BeforPhotoIssue { get; set; }
|
||||||
|
public string? BeforPhotoAttachment { get; set; }
|
||||||
|
public string? AfterPhotoIssue { get; set; }
|
||||||
|
public string? AfterPhotoAttachment { get; set; }
|
||||||
|
public string? SignOffName { get; set; }
|
||||||
|
public string? SignOffAttachment { get; set; }
|
||||||
|
public string? SignOffSignature { get; set; }
|
||||||
|
|
||||||
|
public byte[]? RowVersion { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using Data.SeaHavenIndustries;
|
using Data.SeaHavenIndustries;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
using SeaHaven.DataServices.Dto;
|
||||||
using SeaHaven.DataServices.Interfaces;
|
using SeaHaven.DataServices.Interfaces;
|
||||||
using SeaHaven.Services.DTOs;
|
using SeaHaven.Services.DTOs;
|
||||||
using SeaHaven.Services.Interfaces;
|
using SeaHaven.Services.Interfaces;
|
||||||
|
|
@ -141,6 +142,184 @@ namespace SeaHaven.Services.Implementation
|
||||||
return await _dispatchDataService.CountAsync();
|
return await _dispatchDataService.CountAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<DispatchListResultDTO> GetDispatchListAsync(
|
||||||
|
string? search,
|
||||||
|
string? status,
|
||||||
|
int? vendorId,
|
||||||
|
DateTime? dateFrom,
|
||||||
|
DateTime? dateTo,
|
||||||
|
string? sortBy,
|
||||||
|
string? sortDir,
|
||||||
|
int page,
|
||||||
|
int pageSize,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var query = new DispatchListQuery
|
||||||
|
{
|
||||||
|
Search = search,
|
||||||
|
Status = status,
|
||||||
|
VendorId = vendorId,
|
||||||
|
DateFrom = dateFrom,
|
||||||
|
DateTo = dateTo,
|
||||||
|
SortBy = sortBy,
|
||||||
|
SortDir = sortDir,
|
||||||
|
Page = page,
|
||||||
|
PageSize = pageSize
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _dispatchDataService.GetListAsync(query, cancellationToken);
|
||||||
|
|
||||||
|
return new DispatchListResultDTO
|
||||||
|
{
|
||||||
|
Items = result.Items.Select(MapListEntry).ToList(),
|
||||||
|
Total = result.Total,
|
||||||
|
Page = result.Page,
|
||||||
|
PageSize = result.PageSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DispatchDetailResultDTO?> GetDispatchDetailAsync(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var detail = await _dispatchDataService.GetDetailAsync(id, cancellationToken);
|
||||||
|
return detail == null ? null : MapDetail(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DispatchListEntryDTO MapListEntry(DispatchListItemDTO d)
|
||||||
|
{
|
||||||
|
return new DispatchListEntryDTO
|
||||||
|
{
|
||||||
|
Id = d.Id,
|
||||||
|
DispatchNumber = d.DispatchNumber,
|
||||||
|
PONumber = d.PONumber,
|
||||||
|
NTEAmount = d.NTEAmount,
|
||||||
|
Status = d.Status,
|
||||||
|
DispatchedAt = d.DispatchedAt,
|
||||||
|
ScheduledDate = d.ScheduledDate,
|
||||||
|
CompletedDate = d.CompletedDate,
|
||||||
|
AcknowledgedAt = d.AcknowledgedAt,
|
||||||
|
VerifiedAt = d.VerifiedAt,
|
||||||
|
VendorId = d.VendorId,
|
||||||
|
VendorName = d.VendorName,
|
||||||
|
WorkOrderId = d.WorkOrderId,
|
||||||
|
PrimaryWOInternalNumber = d.PrimaryWOInternalNumber,
|
||||||
|
PrimaryWOTitle = d.PrimaryWOTitle,
|
||||||
|
LocationName = d.LocationName,
|
||||||
|
WorkOrderCount = d.WorkOrderCount,
|
||||||
|
PendingUpliftCount = d.PendingUpliftCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DispatchDetailResultDTO MapDetail(DispatchDetailDTO d)
|
||||||
|
{
|
||||||
|
return new DispatchDetailResultDTO
|
||||||
|
{
|
||||||
|
Id = d.Id,
|
||||||
|
DispatchNumber = d.DispatchNumber,
|
||||||
|
PONumber = d.PONumber,
|
||||||
|
NTEAmount = d.NTEAmount,
|
||||||
|
Description = d.Description,
|
||||||
|
Status = d.Status,
|
||||||
|
ScheduledDate = d.ScheduledDate,
|
||||||
|
CompletedDate = d.CompletedDate,
|
||||||
|
DispatchedAt = d.DispatchedAt,
|
||||||
|
AcknowledgedAt = d.AcknowledgedAt,
|
||||||
|
VerifiedAt = d.VerifiedAt,
|
||||||
|
VerifiedBy = d.VerifiedBy,
|
||||||
|
EmailSent = d.EmailSent,
|
||||||
|
WorkOrderId = d.WorkOrderId,
|
||||||
|
Vendor = d.Vendor == null ? null : new DispatchDetailVendorResultDTO
|
||||||
|
{
|
||||||
|
Id = d.Vendor.Id,
|
||||||
|
CompanyName = d.Vendor.CompanyName,
|
||||||
|
ContactName = d.Vendor.ContactName,
|
||||||
|
Email = d.Vendor.Email,
|
||||||
|
Phone = d.Vendor.Phone,
|
||||||
|
Address = d.Vendor.Address,
|
||||||
|
City = d.Vendor.City,
|
||||||
|
State = d.Vendor.State,
|
||||||
|
Zip = d.Vendor.Zip,
|
||||||
|
TradeSpecialties = d.Vendor.TradeSpecialties
|
||||||
|
},
|
||||||
|
PrimaryLocation = d.PrimaryLocation == null ? null : new DispatchDetailLocationResultDTO
|
||||||
|
{
|
||||||
|
Id = d.PrimaryLocation.Id,
|
||||||
|
Name = d.PrimaryLocation.Name,
|
||||||
|
Address1 = d.PrimaryLocation.Address1,
|
||||||
|
City = d.PrimaryLocation.City,
|
||||||
|
State = d.PrimaryLocation.State,
|
||||||
|
Zip = d.PrimaryLocation.Zip
|
||||||
|
},
|
||||||
|
WorkOrders = d.WorkOrders.Select(w => new DispatchDetailLinkedWorkOrderDTO
|
||||||
|
{
|
||||||
|
WorkOrderId = w.WorkOrderId,
|
||||||
|
InternalWONumber = w.InternalWONumber,
|
||||||
|
Title = w.Title,
|
||||||
|
Priority = w.Priority,
|
||||||
|
Status = w.Status,
|
||||||
|
LocationName = w.LocationName,
|
||||||
|
Trade = w.Trade,
|
||||||
|
SubTrade = w.SubTrade,
|
||||||
|
Problem = w.Problem
|
||||||
|
}).ToList(),
|
||||||
|
Checklist = d.Checklist.Select(c => new DispatchDetailChecklistDTO
|
||||||
|
{
|
||||||
|
Id = c.Id,
|
||||||
|
ItemText = c.ItemText,
|
||||||
|
IsCompleted = c.IsCompleted,
|
||||||
|
CompletedBy = c.CompletedBy,
|
||||||
|
CompletedAt = c.CompletedAt,
|
||||||
|
SortOrder = c.SortOrder,
|
||||||
|
WorkOrderId = c.WorkOrderId
|
||||||
|
}).ToList(),
|
||||||
|
Signoffs = d.Signoffs.Select(s => new DispatchDetailSignoffDTO
|
||||||
|
{
|
||||||
|
Id = s.Id,
|
||||||
|
SignoffType = s.SignoffType,
|
||||||
|
Name = s.Name,
|
||||||
|
Signature = s.Signature,
|
||||||
|
SignatureMethod = s.SignatureMethod,
|
||||||
|
SignedAt = s.SignedAt
|
||||||
|
}).ToList(),
|
||||||
|
Comments = d.Comments.Select(c => new DispatchDetailCommentDTO
|
||||||
|
{
|
||||||
|
Id = c.Id,
|
||||||
|
Commenttext = c.Commenttext,
|
||||||
|
Commenter = c.Commenter,
|
||||||
|
CommentType = c.CommentType,
|
||||||
|
RecordType = c.RecordType,
|
||||||
|
CreatedDate = c.CreatedDate,
|
||||||
|
UserId = c.UserId,
|
||||||
|
UserName = c.UserName
|
||||||
|
}).ToList(),
|
||||||
|
UpliftRequests = d.UpliftRequests.Select(u => new DispatchDetailUpliftDTO
|
||||||
|
{
|
||||||
|
Id = u.Id,
|
||||||
|
CurrentNTE = u.CurrentNTE,
|
||||||
|
RequestedNTE = u.RequestedNTE,
|
||||||
|
Delta = u.Delta,
|
||||||
|
VendorReason = u.VendorReason,
|
||||||
|
Status = u.Status,
|
||||||
|
RequiredTier = u.RequiredTier,
|
||||||
|
RequestedByVendorName = u.RequestedByVendorName,
|
||||||
|
RequestedAt = u.RequestedAt,
|
||||||
|
DecidedAt = u.DecidedAt,
|
||||||
|
DecisionNote = u.DecisionNote,
|
||||||
|
DecidedByName = u.DecidedByName
|
||||||
|
}).ToList(),
|
||||||
|
AuditLog = d.AuditLog.Select(a => new DispatchDetailAuditLogDTO
|
||||||
|
{
|
||||||
|
Id = a.Id,
|
||||||
|
Action = a.Action,
|
||||||
|
FieldName = a.FieldName,
|
||||||
|
OldValue = a.OldValue,
|
||||||
|
NewValue = a.NewValue,
|
||||||
|
CreatedAt = a.CreatedAt,
|
||||||
|
UserId = a.UserId,
|
||||||
|
UserName = a.UserName
|
||||||
|
}).ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private DispatchDTO MapToDTO(Dispatch dispatch)
|
private DispatchDTO MapToDTO(Dispatch dispatch)
|
||||||
{
|
{
|
||||||
return new DispatchDTO
|
return new DispatchDTO
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,131 @@ namespace SeaHaven.Services.Implementation
|
||||||
return await _followUpDataService.CountAsync();
|
return await _followUpDataService.CountAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<PagedResult<FollowUpListRowDTO>> GetFollowUpListAsync(
|
||||||
|
int page,
|
||||||
|
int pageSize,
|
||||||
|
string? search,
|
||||||
|
string? status,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
status = string.IsNullOrWhiteSpace(status) ? "Open" : status;
|
||||||
|
|
||||||
|
var items = await _followUpDataService.GetListPagedAsync(page, pageSize, search, status, cancellationToken);
|
||||||
|
var totalCount = await _followUpDataService.GetListTotalAsync(search, status, cancellationToken);
|
||||||
|
|
||||||
|
return new PagedResult<FollowUpListRowDTO>
|
||||||
|
{
|
||||||
|
Items = items.Select(MapToListRow).ToList(),
|
||||||
|
TotalCount = totalCount,
|
||||||
|
Page = page,
|
||||||
|
PageSize = pageSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<FollowUpDetailDTO?> GetFollowUpDetailAsync(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var followUp = await _followUpDataService.GetDetailByIdAsync(id, cancellationToken);
|
||||||
|
if (followUp == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var createdByName = await _followUpDataService.GetUserNameAsync(followUp.createdby, cancellationToken);
|
||||||
|
|
||||||
|
return new FollowUpDetailDTO
|
||||||
|
{
|
||||||
|
Id = followUp.Id,
|
||||||
|
Reason = followUp.Reason,
|
||||||
|
ScheduleDate = followUp.ScheduleDate,
|
||||||
|
ScheduleStartTime = followUp.ScheduleStartTime,
|
||||||
|
EmployeeId = followUp.EmployeeId,
|
||||||
|
WorkOrderId = followUp.WorkOrderId,
|
||||||
|
AccountId = followUp.AccountId,
|
||||||
|
LocationId = followUp.LocationId,
|
||||||
|
AccountName = followUp.Account?.Name ?? "",
|
||||||
|
AssignedTo = followUp.Employee != null
|
||||||
|
? $"{followUp.Employee.FirstName} {followUp.Employee.LastName}"
|
||||||
|
: "",
|
||||||
|
LocationName = followUp.Location?.Name ?? "",
|
||||||
|
WorkOrderNumber = "",
|
||||||
|
InternalWONumber = "",
|
||||||
|
Status = followUp.Status,
|
||||||
|
CreatedDate = followUp.CreatedDate,
|
||||||
|
CreatedById = followUp.createdby,
|
||||||
|
CreatedByName = createdByName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> CreateOpenFollowUpAsync(FollowUpCreateRequestDTO dto, string userId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var followUp = new FollowUps
|
||||||
|
{
|
||||||
|
Reason = dto.Reason,
|
||||||
|
ScheduleDate = dto.ScheduleDate,
|
||||||
|
ScheduleStartTime = dto.ScheduleStartTime,
|
||||||
|
EmployeeId = dto.EmployeeId,
|
||||||
|
WorkOrderId = dto.WorkOrderId,
|
||||||
|
AccountId = dto.AccountId,
|
||||||
|
LocationId = dto.LocationId,
|
||||||
|
Status = "Open",
|
||||||
|
createdby = userId
|
||||||
|
};
|
||||||
|
|
||||||
|
var created = await _followUpDataService.AddAsync(followUp, cancellationToken);
|
||||||
|
return created.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateFollowUpFieldsAsync(int id, FollowUpUpdateRequestDTO dto, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var followUp = await _followUpDataService.GetByIdForUpdateAsync(id, cancellationToken);
|
||||||
|
if (followUp == null)
|
||||||
|
throw new KeyNotFoundException($"Follow-Up with ID {id} not found");
|
||||||
|
|
||||||
|
followUp.Reason = dto.Reason;
|
||||||
|
followUp.ScheduleDate = dto.ScheduleDate;
|
||||||
|
followUp.ScheduleStartTime = dto.ScheduleStartTime;
|
||||||
|
followUp.EmployeeId = dto.EmployeeId;
|
||||||
|
followUp.WorkOrderId = dto.WorkOrderId;
|
||||||
|
followUp.AccountId = dto.AccountId;
|
||||||
|
followUp.LocationId = dto.LocationId;
|
||||||
|
|
||||||
|
await _followUpDataService.UpdateAsync(followUp, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> SetFollowUpStatusAsync(int id, string status, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return _followUpDataService.SetStatusAsync(id, status, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> RemoveFollowUpAsync(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return _followUpDataService.RemoveByIdAsync(id, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FollowUpListRowDTO MapToListRow(FollowUps followUp)
|
||||||
|
{
|
||||||
|
return new FollowUpListRowDTO
|
||||||
|
{
|
||||||
|
Id = followUp.Id,
|
||||||
|
Reason = followUp.Reason,
|
||||||
|
ScheduleDate = followUp.ScheduleDate,
|
||||||
|
ScheduleStartTime = followUp.ScheduleStartTime,
|
||||||
|
EmployeeId = followUp.EmployeeId,
|
||||||
|
WorkOrderId = followUp.WorkOrderId,
|
||||||
|
AccountId = followUp.AccountId,
|
||||||
|
LocationId = followUp.LocationId,
|
||||||
|
AccountName = followUp.Account?.Name ?? "",
|
||||||
|
AssignedTo = followUp.Employee != null
|
||||||
|
? $"{followUp.Employee.FirstName} {followUp.Employee.LastName}"
|
||||||
|
: "",
|
||||||
|
LocationName = followUp.Location?.Name ?? "",
|
||||||
|
WorkOrderNumber = "",
|
||||||
|
InternalWONumber = "",
|
||||||
|
Status = followUp.Status,
|
||||||
|
CreatedDate = followUp.CreatedDate,
|
||||||
|
CreatedById = null,
|
||||||
|
CreatedByName = ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private FollowUpDTO MapToDTO(FollowUps followUp)
|
private FollowUpDTO MapToDTO(FollowUps followUp)
|
||||||
{
|
{
|
||||||
return new FollowUpDTO
|
return new FollowUpDTO
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,70 @@ namespace SeaHaven.Services.Implementation
|
||||||
return await _locationDataService.CountAsync();
|
return await _locationDataService.CountAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<PagedResult<LocationDTO>> GetLocationListPagedAsync(
|
||||||
|
int page,
|
||||||
|
int pageSize,
|
||||||
|
string? search,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var (items, totalCount) = await _locationDataService.GetListPagedAsync(page, pageSize, search, cancellationToken);
|
||||||
|
return new PagedResult<LocationDTO>
|
||||||
|
{
|
||||||
|
Items = items.Select(MapToDTO).ToList(),
|
||||||
|
TotalCount = totalCount,
|
||||||
|
Page = page,
|
||||||
|
PageSize = pageSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LocationDTO?> GetLocationDetailAsync(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var location = await _locationDataService.GetDetailByIdAsync(id, cancellationToken);
|
||||||
|
return location == null ? null : MapToDTO(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateLocationFromRequestAsync(LocationCreateRequestDTO request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var location = new Locations
|
||||||
|
{
|
||||||
|
Name = request.Name,
|
||||||
|
Title = request.Title,
|
||||||
|
Address1 = request.Address,
|
||||||
|
City = request.City,
|
||||||
|
State = request.State,
|
||||||
|
Zip = request.ZipCode,
|
||||||
|
PhoneNumber = request.Phone,
|
||||||
|
Email = request.ContactEmail,
|
||||||
|
Status = request.Status
|
||||||
|
};
|
||||||
|
|
||||||
|
await _locationDataService.AddAsync(location, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateLocationFromRequestAsync(int id, LocationUpdateRequestDTO request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var location = await _locationDataService.GetByIdForUpdateAsync(id, cancellationToken);
|
||||||
|
if (location == null)
|
||||||
|
throw new KeyNotFoundException($"Location with ID {id} not found");
|
||||||
|
|
||||||
|
location.Name = request.Name;
|
||||||
|
location.Title = request.Title;
|
||||||
|
location.Address1 = request.Address;
|
||||||
|
location.City = request.City;
|
||||||
|
location.State = request.State;
|
||||||
|
location.Zip = request.ZipCode;
|
||||||
|
location.PhoneNumber = request.Phone;
|
||||||
|
location.Email = request.ContactEmail;
|
||||||
|
location.Status = request.Status;
|
||||||
|
|
||||||
|
await _locationDataService.UpdateAsync(location, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> DeleteLocationByIdAsync(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return _locationDataService.DeleteByIdAsync(id, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
// Private helper methods
|
// Private helper methods
|
||||||
private LocationDTO MapToDTO(Locations location)
|
private LocationDTO MapToDTO(Locations location)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
166
SeaHaven.Services/Implementation/SettingsService.cs
Normal file
166
SeaHaven.Services/Implementation/SettingsService.cs
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
using Data.SeaHavenIndustries;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using SeaHaven.DataServices.Dto;
|
||||||
|
using SeaHaven.DataServices.Interfaces;
|
||||||
|
using SeaHaven.Services.DTOs;
|
||||||
|
using SeaHaven.Services.Interfaces;
|
||||||
|
|
||||||
|
namespace SeaHaven.Services.Implementation
|
||||||
|
{
|
||||||
|
public class SettingsService : ISettingsService
|
||||||
|
{
|
||||||
|
private readonly ISettingsDataService _dataService;
|
||||||
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
|
||||||
|
public SettingsService(ISettingsDataService dataService, UserManager<ApplicationUser> userManager)
|
||||||
|
{
|
||||||
|
_dataService = dataService;
|
||||||
|
_userManager = userManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddCategoryAsync(string? name, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await _dataService.AddCategoryAsync(name ?? "", cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> UpdateCategoryAsync(int? id, string? name, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return _dataService.UpdateCategoryAsync(id ?? 0, name ?? "", cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> DeleteCategoryAsync(int id, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return _dataService.DeleteCategoryCascadeAsync(id, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(IReadOnlyList<SettingsCategoryDTO> Items, int Total)> GetCategoryListAsync(int page, int pageSize, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var (items, total) = await _dataService.GetCategoryListPagedAsync(page, pageSize, cancellationToken);
|
||||||
|
return (items.Select(c => new SettingsCategoryDTO { Id = c.Id, Name = c.Name }).ToList(), total);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(IReadOnlyList<SettingsTemplateDTO> Items, int Total)> GetTemplatesAsync(int page, int pageSize, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var (items, total) = await _dataService.GetTemplatesPagedAsync(page, pageSize, cancellationToken);
|
||||||
|
return (items.Select(MapTemplate).ToList(), total);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(IReadOnlyList<SettingsUserDTO> Users, int Total)> GetUsersAsync(string? search, int page, int pageSize, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var (users, total) = await _dataService.GetUsersPagedAsync(search, page, pageSize, cancellationToken);
|
||||||
|
|
||||||
|
var filtered = new List<SettingsUserDTO>();
|
||||||
|
foreach (var user in users)
|
||||||
|
{
|
||||||
|
var roles = await _userManager.GetRolesAsync(user);
|
||||||
|
if (!roles.Contains("Admin"))
|
||||||
|
{
|
||||||
|
filtered.Add(new SettingsUserDTO
|
||||||
|
{
|
||||||
|
RoleName = roles.Count() > 0 ? roles.First() : "",
|
||||||
|
Email = user.Email,
|
||||||
|
UserName = user.FirstName,
|
||||||
|
Date = user.CreatedDate?.Date.ToString(),
|
||||||
|
Status = user.UniqueName,
|
||||||
|
Id = user.Id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (filtered, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<RoleDTO>> GetRolesAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var names = await _dataService.GetRoleNamesAsync(cancellationToken);
|
||||||
|
var roles = new List<RoleDTO>();
|
||||||
|
foreach (var name in names)
|
||||||
|
{
|
||||||
|
if (name == "Admin")
|
||||||
|
{
|
||||||
|
roles.Add(new RoleDTO { Name = name, Description = "Full Access" });
|
||||||
|
}
|
||||||
|
else if (name == "Supervisor")
|
||||||
|
{
|
||||||
|
roles.Add(new RoleDTO { Name = name, Description = "Access to Team's Work Orders Only" });
|
||||||
|
}
|
||||||
|
else if (name == "Manager")
|
||||||
|
{
|
||||||
|
roles.Add(new RoleDTO { Name = name, Description = "Full access accept quotes" });
|
||||||
|
}
|
||||||
|
else if (name == "User")
|
||||||
|
{
|
||||||
|
roles.Add(new RoleDTO { Name = name, Description = "Access to Assigned Work Orders Only" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SettingsTemplateDTO MapTemplate(WorkOrder w)
|
||||||
|
{
|
||||||
|
return new SettingsTemplateDTO
|
||||||
|
{
|
||||||
|
Id = w.Id,
|
||||||
|
IsDeleted = w.IsDeleted,
|
||||||
|
createdby = w.createdby,
|
||||||
|
DeleterUserId = w.DeleterUserId,
|
||||||
|
DeletionTime = w.DeletionTime,
|
||||||
|
CreatedDate = w.CreatedDate,
|
||||||
|
LastModificationTime = w.LastModificationTime,
|
||||||
|
LastModifierUserId = w.LastModifierUserId,
|
||||||
|
InternalWONumber = w.InternalWONumber,
|
||||||
|
ExternalWorkOrderId = w.ExternalWorkOrderId,
|
||||||
|
WorkerOrderNumber = w.WorkerOrderNumber,
|
||||||
|
WorkerOrderTitle = w.WorkerOrderTitle,
|
||||||
|
Description = w.Description,
|
||||||
|
Customer = w.Customer,
|
||||||
|
SiteCode = w.SiteCode,
|
||||||
|
Building = w.Building,
|
||||||
|
Severity = w.Severity,
|
||||||
|
DueDate = w.DueDate,
|
||||||
|
AssignTo = w.AssignTo,
|
||||||
|
LocationId = w.LocationId,
|
||||||
|
LifecycleStatus = w.LifecycleStatus,
|
||||||
|
LegacyStatus = w.LegacyStatus,
|
||||||
|
WorkOrderType = w.WorkOrderType,
|
||||||
|
Service = w.Service,
|
||||||
|
PrimaryDispatchId = w.PrimaryDispatchId,
|
||||||
|
ScheduledDate = w.ScheduledDate,
|
||||||
|
ScheduledStart = w.ScheduledStart,
|
||||||
|
ScheduledEnd = w.ScheduledEnd,
|
||||||
|
TargetWeek = w.TargetWeek,
|
||||||
|
ScheduleWeekOnly = w.ScheduleWeekOnly,
|
||||||
|
OriginalWeek = w.OriginalWeek,
|
||||||
|
OriginalDate = w.OriginalDate,
|
||||||
|
RescheduleCount = w.RescheduleCount,
|
||||||
|
CarriedOver = w.CarriedOver,
|
||||||
|
OperationalFlags = w.OperationalFlags,
|
||||||
|
DocStatus = w.DocStatus,
|
||||||
|
AvettaTask = w.AvettaTask,
|
||||||
|
AssignDate = w.AssignDate,
|
||||||
|
DateReported = w.DateReported,
|
||||||
|
SourceEmailS3Key = w.SourceEmailS3Key,
|
||||||
|
Problem = w.Problem,
|
||||||
|
Trade = w.Trade,
|
||||||
|
SubTrade = w.SubTrade,
|
||||||
|
VendorNTE = w.VendorNTE,
|
||||||
|
CompletedDate = w.CompletedDate,
|
||||||
|
Source = w.Source,
|
||||||
|
istemplate = w.istemplate,
|
||||||
|
PO = w.PO,
|
||||||
|
TT = w.TT,
|
||||||
|
Priority = w.Priority,
|
||||||
|
Status = w.Status,
|
||||||
|
Attachments = w.Attachments,
|
||||||
|
BeforPhotoIssue = w.BeforPhotoIssue,
|
||||||
|
BeforPhotoAttachment = w.BeforPhotoAttachment,
|
||||||
|
AfterPhotoIssue = w.AfterPhotoIssue,
|
||||||
|
AfterPhotoAttachment = w.AfterPhotoAttachment,
|
||||||
|
SignOffName = w.SignOffName,
|
||||||
|
SignOffAttachment = w.SignOffAttachment,
|
||||||
|
SignOffSignature = w.SignOffSignature,
|
||||||
|
RowVersion = w.RowVersion
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,5 +15,8 @@ namespace SeaHaven.Services.Interfaces
|
||||||
Task<bool> DispatchExistsAsync(int id);
|
Task<bool> DispatchExistsAsync(int id);
|
||||||
Task<string> GenerateNextDispatchNumberAsync();
|
Task<string> GenerateNextDispatchNumberAsync();
|
||||||
Task<int> GetTotalDispatchCountAsync();
|
Task<int> GetTotalDispatchCountAsync();
|
||||||
|
|
||||||
|
Task<DispatchListResultDTO> GetDispatchListAsync(string? search, string? status, int? vendorId, DateTime? dateFrom, DateTime? dateTo, string? sortBy, string? sortDir, int page, int pageSize, CancellationToken cancellationToken);
|
||||||
|
Task<DispatchDetailResultDTO?> GetDispatchDetailAsync(int id, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,12 @@ namespace SeaHaven.Services.Interfaces
|
||||||
Task DeleteFollowUpAsync(int id, string userId);
|
Task DeleteFollowUpAsync(int id, string userId);
|
||||||
Task<bool> FollowUpExistsAsync(int id);
|
Task<bool> FollowUpExistsAsync(int id);
|
||||||
Task<int> GetTotalFollowUpCountAsync();
|
Task<int> GetTotalFollowUpCountAsync();
|
||||||
|
|
||||||
|
Task<PagedResult<FollowUpListRowDTO>> GetFollowUpListAsync(int page, int pageSize, string? search, string? status, CancellationToken cancellationToken);
|
||||||
|
Task<FollowUpDetailDTO?> GetFollowUpDetailAsync(int id, CancellationToken cancellationToken);
|
||||||
|
Task<int> CreateOpenFollowUpAsync(FollowUpCreateRequestDTO dto, string userId, CancellationToken cancellationToken);
|
||||||
|
Task UpdateFollowUpFieldsAsync(int id, FollowUpUpdateRequestDTO dto, CancellationToken cancellationToken);
|
||||||
|
Task<bool> SetFollowUpStatusAsync(int id, string status, CancellationToken cancellationToken);
|
||||||
|
Task<bool> RemoveFollowUpAsync(int id, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,11 @@ namespace SeaHaven.Services.Interfaces
|
||||||
Task DeleteLocationAsync(int id, string userId);
|
Task DeleteLocationAsync(int id, string userId);
|
||||||
Task<bool> LocationExistsAsync(int id);
|
Task<bool> LocationExistsAsync(int id);
|
||||||
Task<int> GetTotalLocationCountAsync();
|
Task<int> GetTotalLocationCountAsync();
|
||||||
|
|
||||||
|
Task<PagedResult<LocationDTO>> GetLocationListPagedAsync(int page, int pageSize, string? search, CancellationToken cancellationToken);
|
||||||
|
Task<LocationDTO?> GetLocationDetailAsync(int id, CancellationToken cancellationToken);
|
||||||
|
Task CreateLocationFromRequestAsync(LocationCreateRequestDTO request, CancellationToken cancellationToken);
|
||||||
|
Task UpdateLocationFromRequestAsync(int id, LocationUpdateRequestDTO request, CancellationToken cancellationToken);
|
||||||
|
Task<bool> DeleteLocationByIdAsync(int id, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
SeaHaven.Services/Interfaces/ISettingsService.cs
Normal file
15
SeaHaven.Services/Interfaces/ISettingsService.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
using SeaHaven.Services.DTOs;
|
||||||
|
|
||||||
|
namespace SeaHaven.Services.Interfaces
|
||||||
|
{
|
||||||
|
public interface ISettingsService
|
||||||
|
{
|
||||||
|
Task AddCategoryAsync(string? name, CancellationToken cancellationToken);
|
||||||
|
Task<bool> UpdateCategoryAsync(int? id, string? name, CancellationToken cancellationToken);
|
||||||
|
Task<bool> DeleteCategoryAsync(int id, CancellationToken cancellationToken);
|
||||||
|
Task<(IReadOnlyList<SettingsCategoryDTO> Items, int Total)> GetCategoryListAsync(int page, int pageSize, CancellationToken cancellationToken);
|
||||||
|
Task<(IReadOnlyList<SettingsTemplateDTO> Items, int Total)> GetTemplatesAsync(int page, int pageSize, CancellationToken cancellationToken);
|
||||||
|
Task<(IReadOnlyList<SettingsUserDTO> Users, int Total)> GetUsersAsync(string? search, int page, int pageSize, CancellationToken cancellationToken);
|
||||||
|
Task<IReadOnlyList<RoleDTO>> GetRolesAsync(CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue