mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 08:23:12 +00:00
Merge branch 'main' into dev
This commit is contained in:
commit
12fd5efe8b
127 changed files with 8454 additions and 1699 deletions
34
.env.example
Normal file
34
.env.example
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Sea Haven Industries — shoc-backend required configuration
|
||||
#
|
||||
# This file documents the secrets that used to be hardcoded in appsettings*.json
|
||||
# and source files. Copy the values into one of the supported configuration sources;
|
||||
# do NOT commit real values.
|
||||
#
|
||||
# .NET resolves configuration in this order (later wins):
|
||||
# 1. appsettings.json / appsettings.{Environment}.json (committed — placeholders only)
|
||||
# 2. User Secrets (local dev): dotnet user-secrets set "Key:Sub" "value"
|
||||
# 3. Environment variables (use "__" as the section separator)
|
||||
#
|
||||
# Environment-variable form is shown below. In AWS Elastic Beanstalk these map to
|
||||
# environment properties; locally you can export them or use dotnet user-secrets.
|
||||
#
|
||||
# AWS credentials for the S3 client are NOT listed here on purpose: UploadFileHp now
|
||||
# uses the AWS SDK default credential chain (env AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY,
|
||||
# the shared profile/SSO, or the EC2/ECS instance role). Prefer an instance role in prod.
|
||||
|
||||
# --- SQL Server connection string (Api.SeaHavenIndustries + SeaHavenIndustries) ---
|
||||
ConnectionStrings__DefaultConnection=Server=<host>;Initial Catalog=<db>;User Id=<user>;Password=<password>;MultipleActiveResultSets=true
|
||||
|
||||
# --- SendGrid (transactional email) ---
|
||||
SendGrid__ApiKey=SG.xxxxxxxxxxxxxxxxxxxxxx
|
||||
|
||||
# --- JWT signing secret (Api.SeaHavenIndustries) ---
|
||||
JWT__Secret=<a-long-random-secret>
|
||||
|
||||
# --- Google Maps / Places API key (SeaHavenIndustries Blazor app) ---
|
||||
GoogleMaps__ApiKey=AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
|
||||
# --- AWS S3 (optional; prefer instance role / SSO over static keys) ---
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
# AWS_SECRET_ACCESS_KEY=
|
||||
# AWS_REGION=us-east-2
|
||||
11
.github/workflows/ci.yml
vendored
Normal file
11
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
name: CI
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, dev]
|
||||
jobs:
|
||||
ci:
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/ci-dotnet.yaml@main
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
solution: "SeaHavenIndustries.sln"
|
||||
run-tests: true
|
||||
6
.github/workflows/dependency-review.yml
vendored
Normal file
6
.github/workflows/dependency-review.yml
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
name: Dependency Review
|
||||
on:
|
||||
pull_request:
|
||||
jobs:
|
||||
review:
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/callable-dependency-review.yaml@main
|
||||
11
.github/workflows/labeler.yml
vendored
Normal file
11
.github/workflows/labeler.yml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
name: Labeler
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
jobs:
|
||||
label:
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/callable-labeler.yaml@main
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<ProjectReference Include="..\Api.SeaHavenIndustries\Api.SeaHavenIndustries.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
206
Api.SeaHavenIndustries.Tests/CalendarControllerTests.cs
Normal file
206
Api.SeaHavenIndustries.Tests/CalendarControllerTests.cs
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
using System.Collections;
|
||||
using Api.SeaHavenIndustries.Controllers;
|
||||
using Api.SeaHavenIndustries.DTOs;
|
||||
using Data.SeaHavenIndustries;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Api.SeaHavenIndustries.Tests;
|
||||
|
||||
public class CalendarControllerTests
|
||||
{
|
||||
private static ApplicationDbContext NewContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
return new ApplicationDbContext(options);
|
||||
}
|
||||
|
||||
private static UserManager<ApplicationUser> MockUserManager()
|
||||
{
|
||||
var store = new Mock<IUserStore<ApplicationUser>>();
|
||||
return new Mock<UserManager<ApplicationUser>>(
|
||||
store.Object, null!, null!, null!, null!, null!, null!, null!, null!).Object;
|
||||
}
|
||||
|
||||
private static CalendarController NewController(ApplicationDbContext ctx) =>
|
||||
new(MockUserManager(), ctx);
|
||||
|
||||
private static EditEvent_DTO ValidDto(int? id = null, string title = "Standup") => new()
|
||||
{
|
||||
Id = id,
|
||||
Title = title,
|
||||
ReminderMinutes = 15,
|
||||
EventColor = "primary",
|
||||
StartDate = new DateTime(2026, 6, 1),
|
||||
StartTime = "08:00 AM",
|
||||
EndDate = new DateTime(2026, 6, 1),
|
||||
EndTime = "08:30 AM"
|
||||
};
|
||||
|
||||
private static Events SeedEvent(ApplicationDbContext ctx, string title, DateTime startDate, bool? isDeleted = false)
|
||||
{
|
||||
var ev = new Events
|
||||
{
|
||||
Title = title,
|
||||
EventColor = "primary",
|
||||
ReminderMinutes = 15,
|
||||
StartDate = startDate,
|
||||
EndDate = startDate,
|
||||
IsDeleted = isDeleted,
|
||||
CreatedDate = DateTime.Now
|
||||
};
|
||||
ctx.Events.Add(ev);
|
||||
ctx.SaveChanges();
|
||||
return ev;
|
||||
}
|
||||
|
||||
private static object Prop(object source, string name) =>
|
||||
source.GetType().GetProperty(name)!.GetValue(source)!;
|
||||
|
||||
[Fact]
|
||||
public async Task AddEvent_WhenValid_PersistsEventAndReturnsOk()
|
||||
{
|
||||
using var ctx = NewContext();
|
||||
var result = await NewController(ctx).AddEvent(ValidDto());
|
||||
|
||||
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||
ok.Value.Should().BeOfType<DataResponse>().Which.Status.Should().Be("200");
|
||||
|
||||
var saved = ctx.Events.Single();
|
||||
saved.Title.Should().Be("Standup");
|
||||
saved.IsDeleted.Should().Be(false);
|
||||
saved.CreatedDate.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EditEvent_WhenIdMissing_ReturnsBadRequest()
|
||||
{
|
||||
using var ctx = NewContext();
|
||||
var result = await NewController(ctx).EditEvent(ValidDto(id: null));
|
||||
|
||||
result.Should().BeOfType<BadRequestObjectResult>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EditEvent_WhenEventNotFound_ReturnsNotFound()
|
||||
{
|
||||
using var ctx = NewContext();
|
||||
var result = await NewController(ctx).EditEvent(ValidDto(id: 999));
|
||||
|
||||
result.Should().BeOfType<NotFoundObjectResult>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EditEvent_WhenEventExists_UpdatesFieldsAndStampsModified()
|
||||
{
|
||||
using var ctx = NewContext();
|
||||
var existing = SeedEvent(ctx, "Old", new DateTime(2026, 6, 1));
|
||||
|
||||
var result = await NewController(ctx).EditEvent(ValidDto(id: existing.Id, title: "New"));
|
||||
|
||||
result.Should().BeOfType<OkObjectResult>();
|
||||
var updated = ctx.Events.Single();
|
||||
updated.Title.Should().Be("New");
|
||||
updated.LastModificationTime.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetEventById_WhenExists_ReturnsEventDto()
|
||||
{
|
||||
using var ctx = NewContext();
|
||||
var existing = SeedEvent(ctx, "Review", new DateTime(2026, 6, 1));
|
||||
|
||||
var result = await NewController(ctx).GetEventById(existing.Id);
|
||||
|
||||
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||
var dto = ok.Value.Should().BeOfType<DataResponse>().Subject.Data as Event_DTO;
|
||||
dto!.Title.Should().Be("Review");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetEventById_WhenSoftDeleted_ReturnsNotFound()
|
||||
{
|
||||
using var ctx = NewContext();
|
||||
var deleted = SeedEvent(ctx, "Gone", new DateTime(2026, 6, 1), isDeleted: true);
|
||||
|
||||
var result = await NewController(ctx).GetEventById(deleted.Id);
|
||||
|
||||
result.Should().BeOfType<NotFoundObjectResult>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteEvent_WhenExists_SoftDeletesAndKeepsRow()
|
||||
{
|
||||
using var ctx = NewContext();
|
||||
var existing = SeedEvent(ctx, "Cancel", new DateTime(2026, 6, 1));
|
||||
|
||||
var result = await NewController(ctx).DeleteEvent(existing.Id);
|
||||
|
||||
result.Should().BeOfType<OkObjectResult>();
|
||||
var row = ctx.Events.Single();
|
||||
row.IsDeleted.Should().Be(true);
|
||||
row.DeletionTime.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteEvent_WhenNotFound_ReturnsNotFound()
|
||||
{
|
||||
using var ctx = NewContext();
|
||||
var result = await NewController(ctx).DeleteEvent(12345);
|
||||
|
||||
result.Should().BeOfType<NotFoundObjectResult>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEventList_ExcludesDeletedAndCountsAfterFilter()
|
||||
{
|
||||
using var ctx = NewContext();
|
||||
SeedEvent(ctx, "Alpha meeting", new DateTime(2026, 6, 1));
|
||||
SeedEvent(ctx, "Beta meeting", new DateTime(2026, 6, 2));
|
||||
SeedEvent(ctx, "Alpha review", new DateTime(2026, 6, 3));
|
||||
SeedEvent(ctx, "Alpha archived", new DateTime(2026, 6, 4), isDeleted: true);
|
||||
|
||||
var result = NewController(ctx).GetEventList(search: "alpha", page: 1, pageSize: 10);
|
||||
|
||||
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||
var data = ok.Value.Should().BeOfType<DataResponse>().Subject.Data!;
|
||||
((int)Prop(data, "totalCount")).Should().Be(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEventList_OrdersByStartDateDescending()
|
||||
{
|
||||
using var ctx = NewContext();
|
||||
SeedEvent(ctx, "Earliest", new DateTime(2026, 6, 1));
|
||||
SeedEvent(ctx, "Latest", new DateTime(2026, 6, 10));
|
||||
SeedEvent(ctx, "Middle", new DateTime(2026, 6, 5));
|
||||
|
||||
var result = NewController(ctx).GetEventList(page: 1, pageSize: 10);
|
||||
|
||||
var data = (result as OkObjectResult)!.Value.As<DataResponse>().Data!;
|
||||
var rows = ((IEnumerable)Prop(data, "rows")).Cast<Event_DTO>().ToList();
|
||||
rows.Select(r => r.Title).Should().ContainInOrder("Latest", "Middle", "Earliest");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEventsForCalendar_ReturnsOnlyEventsOverlappingRange()
|
||||
{
|
||||
using var ctx = NewContext();
|
||||
SeedEvent(ctx, "InRange", new DateTime(2026, 6, 15));
|
||||
SeedEvent(ctx, "BeforeRange", new DateTime(2026, 5, 1));
|
||||
SeedEvent(ctx, "AfterRange", new DateTime(2026, 7, 1));
|
||||
|
||||
var result = NewController(ctx).GetEventsForCalendar(
|
||||
start: new DateTime(2026, 6, 1), end: new DateTime(2026, 6, 30));
|
||||
|
||||
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||
var events = ok.Value.As<DataResponse>().Data as IEnumerable<Event_DTO>;
|
||||
events!.Select(e => e.Title).Should().BeEquivalentTo(new[] { "InRange" });
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Data.SeaHavenIndustries\Data.SeaHavenIndustries.csproj" />
|
||||
<ProjectReference Include="..\SeaHaven.Services\SeaHaven.Services.csproj" />
|
||||
<ProjectReference Include="..\SeaHaven.DataServices\SeaHaven.DataServices.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,22 @@
|
|||
using Api.SeaHavenIndustries.DTOs;
|
||||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using System.Security.Claims;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/Account")]
|
||||
[Route("api/accounts")]
|
||||
public class AccountController : ControllerBase
|
||||
{
|
||||
private readonly ApplicationDbContext _db;
|
||||
private readonly IAccountService _accountService;
|
||||
|
||||
public AccountController(ApplicationDbContext db)
|
||||
public AccountController(IAccountService accountService)
|
||||
{
|
||||
_db = db;
|
||||
_accountService = accountService;
|
||||
}
|
||||
|
||||
// -----------------------------------
|
||||
|
|
@ -23,34 +27,19 @@ public class AccountController : ControllerBase
|
|||
{
|
||||
try
|
||||
{
|
||||
var model = new Accounts
|
||||
var createDto = new CreateAccountDTO
|
||||
{
|
||||
Name = dto.Name,
|
||||
Type = dto.Type,
|
||||
Importance = dto.Importance,
|
||||
TerritoryId = dto.TerritoryId,
|
||||
OwnerEmployeeId = dto.OwnerEmployeeId,
|
||||
PrivacySetting = dto.PrivacySetting,
|
||||
About = dto.About,
|
||||
Tags = dto.Tags,
|
||||
|
||||
Name = dto.Name ?? throw new ArgumentException("Name is required"),
|
||||
Phone = dto.Phone,
|
||||
Email = dto.Email,
|
||||
IndustryId = dto.IndustryId,
|
||||
TimeZone = dto.TimeZone,
|
||||
Website = dto.Website,
|
||||
CurrencyCode = dto.CurrencyCode,
|
||||
ServiceInstructions = dto.ServiceInstructions,
|
||||
|
||||
FacebookUrl = dto.FacebookUrl,
|
||||
LinkedInUrl = dto.LinkedInUrl,
|
||||
TwitterUrl = dto.TwitterUrl,
|
||||
|
||||
CreatedDate = DateTime.UtcNow
|
||||
Website = dto.Website
|
||||
};
|
||||
|
||||
_db.Accounts.Add(model);
|
||||
await _db.SaveChangesAsync();
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
await _accountService.CreateAccountAsync(createDto, userId);
|
||||
|
||||
return Ok(new DataResponse
|
||||
{
|
||||
|
|
@ -58,13 +47,14 @@ public class AccountController : ControllerBase
|
|||
Status = "200"
|
||||
});
|
||||
}
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response
|
||||
{
|
||||
Status = "Error",
|
||||
Message = ex.Message
|
||||
});
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,34 +66,19 @@ public class AccountController : ControllerBase
|
|||
{
|
||||
try
|
||||
{
|
||||
var account = await _db.Accounts.FirstOrDefaultAsync(a => a.Id == dto.Id);
|
||||
if (account == null)
|
||||
return BadRequest(new Response { Status = "Error", Message = "Account not found." });
|
||||
var updateDto = new UpdateAccountDTO
|
||||
{
|
||||
Name = dto.Name,
|
||||
Phone = dto.Phone,
|
||||
Email = dto.Email,
|
||||
Website = dto.Website
|
||||
};
|
||||
|
||||
account.Name = dto.Name;
|
||||
account.Type = dto.Type;
|
||||
account.Importance = dto.Importance;
|
||||
account.TerritoryId = dto.TerritoryId;
|
||||
account.OwnerEmployeeId = dto.OwnerEmployeeId;
|
||||
account.PrivacySetting = dto.PrivacySetting;
|
||||
account.About = dto.About;
|
||||
account.Tags = dto.Tags;
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
account.Phone = dto.Phone;
|
||||
account.Email = dto.Email;
|
||||
account.IndustryId = dto.IndustryId;
|
||||
account.TimeZone = dto.TimeZone;
|
||||
account.Website = dto.Website;
|
||||
account.CurrencyCode = dto.CurrencyCode;
|
||||
account.ServiceInstructions = dto.ServiceInstructions;
|
||||
|
||||
account.FacebookUrl = dto.FacebookUrl;
|
||||
account.LinkedInUrl = dto.LinkedInUrl;
|
||||
account.TwitterUrl = dto.TwitterUrl;
|
||||
|
||||
account.LastModificationTime = DateTime.UtcNow;
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
await _accountService.UpdateAccountAsync(dto.Id, updateDto, userId);
|
||||
|
||||
return Ok(new DataResponse
|
||||
{
|
||||
|
|
@ -111,13 +86,18 @@ public class AccountController : ControllerBase
|
|||
Status = "200"
|
||||
});
|
||||
}
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Account not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response
|
||||
{
|
||||
Status = "Error",
|
||||
Message = ex.Message
|
||||
});
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,12 +109,11 @@ public class AccountController : ControllerBase
|
|||
{
|
||||
try
|
||||
{
|
||||
var account = await _db.Accounts.FirstOrDefaultAsync(a => a.Id == id);
|
||||
if (account == null)
|
||||
return BadRequest(new Response { Status = "Error", Message = "Account not found." });
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
_db.Accounts.Remove(account);
|
||||
await _db.SaveChangesAsync();
|
||||
await _accountService.DeleteAccountAsync(id, userId);
|
||||
|
||||
return Ok(new DataResponse
|
||||
{
|
||||
|
|
@ -142,13 +121,13 @@ public class AccountController : ControllerBase
|
|||
Status = "200"
|
||||
});
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Account not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response
|
||||
{
|
||||
Status = "Error",
|
||||
Message = ex.Message
|
||||
});
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -156,49 +135,28 @@ public class AccountController : ControllerBase
|
|||
// GET: api/Account/GetAccountList?search=&page=&pageSize=
|
||||
// -----------------------------------
|
||||
[HttpGet("GetAccountList")]
|
||||
public IActionResult GetAccountList(string? search = "", int page = 1, int pageSize = 12)
|
||||
public async Task<IActionResult> GetAccountList(string? search = "", int page = 1, int pageSize = 12)
|
||||
{
|
||||
search ??= "";
|
||||
var pagedResult = await _accountService.GetAccountsPagedAsync(page, pageSize, search);
|
||||
|
||||
var query = _db.Accounts
|
||||
.Where(a => a.IsDeleted == false || a.IsDeleted == null);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
var data = pagedResult.Items.Select(a => new
|
||||
{
|
||||
var s = search.ToLower();
|
||||
query = query.Where(a =>
|
||||
(a.Name ?? "").ToLower().Contains(s) ||
|
||||
(a.Email ?? "").ToLower().Contains(s) ||
|
||||
(a.Phone ?? "").Contains(s) ||
|
||||
(a.Website ?? "").ToLower().Contains(s)
|
||||
);
|
||||
}
|
||||
|
||||
var totalCount = query.Count();
|
||||
|
||||
var data = query
|
||||
.OrderByDescending(a => a.Id)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(a => new
|
||||
{
|
||||
a.Id,
|
||||
a.Name,
|
||||
a.Type,
|
||||
a.Phone,
|
||||
a.Email,
|
||||
Industry = a.IndustryId,
|
||||
a.Website
|
||||
})
|
||||
.ToList();
|
||||
Id = a.Id,
|
||||
Name = a.Name,
|
||||
Type = a.Type ?? "",
|
||||
Phone = a.Phone,
|
||||
Email = a.Email,
|
||||
Industry = (int?)null, // Map if needed
|
||||
Website = a.Website
|
||||
});
|
||||
|
||||
var viewModel = new Pagination_DTO
|
||||
{
|
||||
Data = data,
|
||||
PageNumber = page,
|
||||
PageSize = pageSize,
|
||||
TotalCount = totalCount,
|
||||
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
|
||||
TotalCount = pagedResult.TotalCount,
|
||||
TotalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize)
|
||||
};
|
||||
|
||||
return Ok(viewModel);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
using Api.SeaHavenIndustries.DTOs;
|
||||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Api.SeaHavenIndustries.Controllers
|
||||
{
|
||||
|
|
@ -13,12 +17,12 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
public class AssetController : Controller
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly ApplicationDbContext _db;
|
||||
|
||||
public AssetController(UserManager<ApplicationUser> userManager, ApplicationDbContext db)
|
||||
private readonly IAssetService _assetService;
|
||||
|
||||
public AssetController(UserManager<ApplicationUser> userManager, IAssetService assetService)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_db = db;
|
||||
_assetService = assetService;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
|
|
@ -27,22 +31,28 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
{
|
||||
try
|
||||
{
|
||||
Assets model = new Assets();
|
||||
model.AssetCode = asset.AssetCode;
|
||||
model.Name = asset.Name;
|
||||
model.Description = asset.Description;
|
||||
model.AccountId = asset.AccountId;
|
||||
model.LocationId = asset.LocationId;
|
||||
model.IsActive = asset.IsActive;
|
||||
|
||||
_db.Assets.Add(model);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
var createDto = new CreateAssetDTO
|
||||
{
|
||||
Name = asset.Name ?? throw new ArgumentException("Name is required"),
|
||||
LocationId = asset.LocationId
|
||||
};
|
||||
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
await _assetService.CreateAssetAsync(createDto, userId);
|
||||
|
||||
return Ok(new DataResponse { Message = "Asset Created Successfully", Status = "200" });
|
||||
}
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,28 +62,32 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
{
|
||||
try
|
||||
{
|
||||
var exist = _db.Assets.Where(w => w.Id == asset.Id).FirstOrDefault();
|
||||
|
||||
if (exist == null)
|
||||
var updateDto = new UpdateAssetDTO
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = "Asset not found!" });
|
||||
}
|
||||
Name = asset.Name,
|
||||
LocationId = asset.LocationId
|
||||
};
|
||||
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
await _assetService.UpdateAssetAsync(asset.Id, updateDto, userId);
|
||||
|
||||
exist.AssetCode = asset.AssetCode;
|
||||
exist.Name = asset.Name;
|
||||
exist.Description = asset.Description;
|
||||
exist.AccountId = asset.AccountId;
|
||||
exist.LocationId = asset.LocationId;
|
||||
exist.IsActive = asset.IsActive;
|
||||
|
||||
_db.Assets.Update(exist);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return Ok(new DataResponse { Message = "Asset Updated Successfully", Status = "200" });
|
||||
}
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Asset not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -83,23 +97,20 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
{
|
||||
try
|
||||
{
|
||||
var exist = _db.Assets.Where(w => w.Id == id).FirstOrDefault();
|
||||
|
||||
if (exist != null)
|
||||
{
|
||||
_db.Remove(exist);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = "Asset not found!" });
|
||||
}
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
await _assetService.DeleteAssetAsync(id, userId!);
|
||||
return Ok(new DataResponse { Message = "Asset Deleted Successfully", Status = "200" });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Asset not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,17 +120,13 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
{
|
||||
try
|
||||
{
|
||||
var asset = await _db.Assets
|
||||
.Include(a => a.Account)
|
||||
.Include(a => a.Location)
|
||||
.Where(w => w.Id == id)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
var asset = await _assetService.GetAssetByIdWithDetailsAsync(id);
|
||||
|
||||
if (asset == null)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Asset not found" });
|
||||
}
|
||||
|
||||
|
||||
return Ok(asset);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -130,48 +137,25 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
|
||||
[HttpGet]
|
||||
[Route("GetAssetList")]
|
||||
public IActionResult GetAssetList(string? search = "", int page = 1, int pageSize = 10)
|
||||
public async Task<IActionResult> GetAssetList(string? search = "", int page = 1, int pageSize = 10)
|
||||
{
|
||||
// Build the query with search filter
|
||||
var query = _db.Assets
|
||||
.Include(a => a.Account)
|
||||
.Include(a => a.Location)
|
||||
.Where(s =>
|
||||
string.IsNullOrEmpty(search) ||
|
||||
(s.Name != null && s.Name.ToLower().Contains(search.ToLower())) ||
|
||||
(s.AssetCode != null && s.AssetCode.ToLower().Contains(search.ToLower())) ||
|
||||
(s.Description != null && s.Description.ToLower().Contains(search.ToLower()))
|
||||
);
|
||||
var pagedResult = await _assetService.GetAssetsPagedAsync(page, pageSize, search);
|
||||
|
||||
// Get total count AFTER applying search filter
|
||||
var totalCount = query.Count();
|
||||
|
||||
// Get paginated data
|
||||
var data = query
|
||||
.Select(n => new
|
||||
{
|
||||
n.Id,
|
||||
n.AssetCode,
|
||||
n.Name,
|
||||
n.Description,
|
||||
n.IsActive,
|
||||
AccountName = n.Account != null ? n.Account.Name : "",
|
||||
LocationName = n.Location != null ? n.Location.Name : ""
|
||||
})
|
||||
.OrderByDescending(d => d.Id)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToList();
|
||||
|
||||
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
||||
var data = pagedResult.Items.Select(a => new
|
||||
{
|
||||
a.Id,
|
||||
a.Name,
|
||||
LocationName = a.LocationName ?? "",
|
||||
a.CreatedDate
|
||||
});
|
||||
|
||||
var viewModel = new Pagination_DTO
|
||||
{
|
||||
Data = data,
|
||||
PageNumber = page,
|
||||
PageSize = pageSize,
|
||||
TotalCount = totalCount,
|
||||
TotalPages = totalPages
|
||||
TotalCount = pagedResult.TotalCount,
|
||||
TotalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize)
|
||||
};
|
||||
|
||||
return Ok(viewModel);
|
||||
|
|
|
|||
|
|
@ -34,36 +34,45 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
[Route("login")]
|
||||
public async Task<IActionResult> Login([FromBody] LoginModel model)
|
||||
{
|
||||
var user = await _userManager.FindByNameAsync(model.Username ?? "");
|
||||
if (user != null && user.IsDeleted != true && await _userManager.CheckPasswordAsync(user, model.Password ?? ""))
|
||||
try
|
||||
{
|
||||
// Standard login without 2FA
|
||||
var userRoles = await _userManager.GetRolesAsync(user);
|
||||
var authClaims = new List<Claim>
|
||||
var user = await _userManager.FindByNameAsync(model.Username ?? "");
|
||||
if (user != null && user.IsDeleted != true && await _userManager.CheckPasswordAsync(user, model.Password ?? ""))
|
||||
{
|
||||
// Standard login without 2FA
|
||||
var userRoles = await _userManager.GetRolesAsync(user);
|
||||
var authClaims = new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.Name, user.UserName ?? ""),
|
||||
new Claim(ClaimTypes.NameIdentifier, user.Id),
|
||||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
||||
};
|
||||
foreach (var userRole in userRoles)
|
||||
{
|
||||
authClaims.Add(new Claim(ClaimTypes.Role, userRole));
|
||||
foreach (var userRole in userRoles)
|
||||
{
|
||||
authClaims.Add(new Claim(ClaimTypes.Role, userRole));
|
||||
}
|
||||
var token = GetToken(authClaims);
|
||||
return Ok(new
|
||||
{
|
||||
token = new JwtSecurityTokenHandler().WriteToken(token),
|
||||
expiration = token.ValidTo,
|
||||
email = user.Email,
|
||||
userRoles = userRoles.FirstOrDefault(),
|
||||
phoneNumber = user.PhoneNumber,
|
||||
fullname = user.FirstName + " " + user.LastName,
|
||||
id = user.Id
|
||||
});
|
||||
}
|
||||
var token = GetToken(authClaims);
|
||||
return Ok(new
|
||||
{
|
||||
token = new JwtSecurityTokenHandler().WriteToken(token),
|
||||
expiration = token.ValidTo,
|
||||
email = user.Email,
|
||||
userRoles = userRoles.FirstOrDefault(),
|
||||
phoneNumber = user.PhoneNumber,
|
||||
fullname = user.FirstName + " " + user.LastName,
|
||||
id = user.Id
|
||||
});
|
||||
}
|
||||
|
||||
// Invalid credentials
|
||||
return Unauthorized();
|
||||
// Invalid credentials
|
||||
return Unauthorized();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
return StatusCode(500, ex.Message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Route("ChangePassword")]
|
||||
|
|
|
|||
|
|
@ -1,22 +1,5 @@
|
|||
/*
|
||||
===================================================================================
|
||||
CALENDAR/EVENTS MODULE - COMPLETE IMPLEMENTATION
|
||||
===================================================================================
|
||||
This file contains everything needed for the Calendar/Events module:
|
||||
1. Events Model (Database Entity)
|
||||
2. Event_DTO (for GET responses)
|
||||
3. EditEvent_DTO (for POST/PUT requests)
|
||||
4. CalendarController (API endpoints)
|
||||
5. SQL Script for database table creation and sample data
|
||||
|
||||
Author: AI Agent
|
||||
Date: 2026-03-30
|
||||
===================================================================================
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Api.SeaHavenIndustries.DTOs;
|
||||
|
|
@ -26,116 +9,9 @@ using Microsoft.AspNetCore.Identity;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
// ===================================================================================
|
||||
// SECTION 1: DATABASE MODEL (Data.SeaHavenIndustries/Models/Events.cs)
|
||||
// ===================================================================================
|
||||
|
||||
namespace Data.SeaHavenIndustries
|
||||
{
|
||||
public class Events : FullAuditEntity
|
||||
{
|
||||
public string Title { get; set; } = null!;
|
||||
public string? Description { get; set; }
|
||||
public string? Location { get; set; }
|
||||
|
||||
// Related Record (can link to Accounts, Contacts, Work Orders, etc.)
|
||||
public int? RecordID { get; set; }
|
||||
public string? RecordName { get; set; }
|
||||
public string? RecordTargetType { get; set; }
|
||||
|
||||
// Reminder Settings
|
||||
public int ReminderMinutes { get; set; } = 15; // Default: 15 minutes before
|
||||
|
||||
// Color for calendar display
|
||||
public string EventColor { get; set; } = "primary"; // primary, success, info, warning, danger
|
||||
|
||||
// Schedule
|
||||
public DateTime StartDate { get; set; }
|
||||
public string? StartTime { get; set; } // e.g., "08:00 AM"
|
||||
public DateTime EndDate { get; set; }
|
||||
public string? EndTime { get; set; } // e.g., "08:30 AM"
|
||||
public bool AllDay { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================================
|
||||
// SECTION 2: DTOs (Api.SeaHavenIndustries/DTOs/Event_DTO.cs)
|
||||
// ===================================================================================
|
||||
|
||||
namespace Api.SeaHavenIndustries.DTOs
|
||||
{
|
||||
// DTO for GET responses
|
||||
public class Event_DTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; } = null!;
|
||||
public string? Description { get; set; }
|
||||
public string? Location { get; set; }
|
||||
|
||||
public int? RecordID { get; set; }
|
||||
public string? RecordName { get; set; }
|
||||
public string? RecordTargetType { get; set; }
|
||||
|
||||
public int ReminderMinutes { get; set; }
|
||||
public string EventColor { get; set; } = null!;
|
||||
|
||||
public DateTime StartDate { get; set; }
|
||||
public string? StartTime { get; set; }
|
||||
public DateTime EndDate { get; set; }
|
||||
public string? EndTime { get; set; }
|
||||
public bool AllDay { get; set; }
|
||||
|
||||
public DateTime? CreatedDate { get; set; }
|
||||
public string? createdby { get; set; }
|
||||
}
|
||||
|
||||
// DTO for POST/PUT requests (Create/Edit)
|
||||
public class EditEvent_DTO
|
||||
{
|
||||
public int? Id { get; set; } // Null for create, populated for edit
|
||||
|
||||
[Required(ErrorMessage = "Please enter a title")]
|
||||
[StringLength(100, ErrorMessage = "Title cannot be longer than 100 characters")]
|
||||
public string Title { get; set; } = null!;
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
[StringLength(150, ErrorMessage = "Location cannot be longer than 150 characters")]
|
||||
public string? Location { get; set; }
|
||||
|
||||
public int? RecordID { get; set; }
|
||||
public string? RecordName { get; set; }
|
||||
public string? RecordTargetType { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "The Remind Attendees field is required")]
|
||||
public int ReminderMinutes { get; set; } = 15;
|
||||
|
||||
[Required(ErrorMessage = "The Select a Calendar Color field is required")]
|
||||
public string EventColor { get; set; } = "primary";
|
||||
|
||||
[Required(ErrorMessage = "Please select a start date")]
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please select a start time")]
|
||||
public string StartTime { get; set; } = null!;
|
||||
|
||||
[Required(ErrorMessage = "Please select an end date")]
|
||||
public DateTime EndDate { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please select an end time")]
|
||||
public string EndTime { get; set; } = null!;
|
||||
|
||||
public bool AllDay { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================================
|
||||
// SECTION 3: CONTROLLER (Api.SeaHavenIndustries/Controllers/CalendarController.cs)
|
||||
// ===================================================================================
|
||||
|
||||
namespace Api.SeaHavenIndustries.Controllers
|
||||
{
|
||||
//[Authorize] // Uncomment when authentication is needed
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/Calendar")]
|
||||
public class CalendarController : Controller
|
||||
|
|
@ -280,7 +156,6 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// ===================================================================================
|
||||
// DELETE EVENT
|
||||
// ===================================================================================
|
||||
|
|
@ -314,21 +189,22 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
// ===================================================================================
|
||||
// GET EVENTS LIST (WITH PAGINATION AND FILTERS)
|
||||
// ===================================================================================
|
||||
[HttpPost]
|
||||
[HttpGet]
|
||||
[Route("GetEventList")]
|
||||
public IActionResult GetEventList(Pagination_DTO dto)
|
||||
public IActionResult GetEventList(string? search = "", int page = 1, int pageSize = 10)
|
||||
{
|
||||
try
|
||||
{
|
||||
var query = _db.Events.Where(e => e.IsDeleted != true);
|
||||
|
||||
// Apply search filter if provided
|
||||
if (!string.IsNullOrWhiteSpace(dto.Search))
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var searchLower = search.ToLower();
|
||||
query = query.Where(e =>
|
||||
e.Title.Contains(dto.Search) ||
|
||||
(e.Description != null && e.Description.Contains(dto.Search)) ||
|
||||
(e.Location != null && e.Location.Contains(dto.Search))
|
||||
e.Title.ToLower().Contains(searchLower) ||
|
||||
(e.Description != null && e.Description.ToLower().Contains(searchLower)) ||
|
||||
(e.Location != null && e.Location.ToLower().Contains(searchLower))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -339,8 +215,8 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
var events = query
|
||||
.OrderByDescending(e => e.StartDate)
|
||||
.ThenByDescending(e => e.CreatedDate)
|
||||
.Skip((dto.PageNumber - 1) * dto.PageSize)
|
||||
.Take(dto.PageSize)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(e => new Event_DTO
|
||||
{
|
||||
Id = e.Id,
|
||||
|
|
@ -428,126 +304,3 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===================================================================================
|
||||
SECTION 4: DATABASE SETUP
|
||||
===================================================================================
|
||||
|
||||
STEP 1: Add Events DbSet to ApplicationDbContext.cs
|
||||
-------------------------------------------------------
|
||||
Add this line in the ApplicationDbContext class (around line 48):
|
||||
|
||||
public DbSet<Events> Events { get; set; }
|
||||
|
||||
|
||||
STEP 2: Create Database Migration
|
||||
-------------------------------------------------------
|
||||
Run these commands in the terminal from the Api.SeaHavenIndustries project directory:
|
||||
|
||||
dotnet ef migrations add AddEventsTable --project ../Data.SeaHavenIndustries
|
||||
dotnet ef database update
|
||||
|
||||
|
||||
STEP 3: SQL Script for Manual Table Creation (Alternative to migrations)
|
||||
-------------------------------------------------------
|
||||
*/
|
||||
|
||||
-- SQL SCRIPT TO CREATE EVENTS TABLE
|
||||
/*
|
||||
CREATE TABLE Events (
|
||||
Id INT PRIMARY KEY IDENTITY(1,1),
|
||||
Title NVARCHAR(100) NOT NULL,
|
||||
Description NVARCHAR(MAX) NULL,
|
||||
Location NVARCHAR(150) NULL,
|
||||
|
||||
RecordID INT NULL,
|
||||
RecordName NVARCHAR(255) NULL,
|
||||
RecordTargetType NVARCHAR(50) NULL,
|
||||
|
||||
ReminderMinutes INT NOT NULL DEFAULT 15,
|
||||
EventColor NVARCHAR(20) NOT NULL DEFAULT 'primary',
|
||||
|
||||
StartDate DATETIME NOT NULL,
|
||||
StartTime NVARCHAR(10) NULL,
|
||||
EndDate DATETIME NOT NULL,
|
||||
EndTime NVARCHAR(10) NULL,
|
||||
AllDay BIT NOT NULL DEFAULT 0,
|
||||
|
||||
-- Audit fields
|
||||
IsDeleted BIT NULL,
|
||||
createdby NVARCHAR(450) NULL,
|
||||
DeleterUserId NVARCHAR(450) NULL,
|
||||
DeletionTime DATETIME NULL,
|
||||
CreatedDate DATETIME NULL,
|
||||
LastModificationTime DATETIME NULL,
|
||||
LastModifierUserId INT NULL
|
||||
);
|
||||
|
||||
-- Insert sample data
|
||||
INSERT INTO Events (Title, Description, Location, StartDate, StartTime, EndDate, EndTime, AllDay, EventColor, ReminderMinutes, CreatedDate, IsDeleted)
|
||||
VALUES
|
||||
('Team Meeting', 'Weekly team sync', 'Conference Room A', '2026-04-01', '10:00 AM', '2026-04-01', '11:00 AM', 0, 'primary', 15, GETDATE(), 0),
|
||||
('Client Presentation', 'Q1 Results Review with ABC Corp', 'Virtual - Zoom', '2026-04-03', '02:00 PM', '2026-04-03', '03:30 PM', 0, 'success', 30, GETDATE(), 0),
|
||||
('Project Deadline', 'Final delivery for Project Phoenix', NULL, '2026-04-05', NULL, '2026-04-05', NULL, 1, 'danger', 1440, GETDATE(), 0),
|
||||
('Training Session', 'New system training for all employees', 'Main Office', '2026-04-08', '09:00 AM', '2026-04-08', '12:00 PM', 0, 'info', 60, GETDATE(), 0),
|
||||
('Equipment Maintenance', 'Scheduled maintenance for all assets', 'Warehouse', '2026-04-10', '08:00 AM', '2026-04-10', '05:00 PM', 0, 'warning', 120, GETDATE(), 0);
|
||||
*/
|
||||
|
||||
/*
|
||||
===================================================================================
|
||||
SECTION 5: IMPLEMENTATION CHECKLIST
|
||||
===================================================================================
|
||||
|
||||
BACKEND SETUP:
|
||||
--------------
|
||||
1. ✓ Create Events.cs in Data.SeaHavenIndustries/Models/
|
||||
2. ✓ Create Event_DTO.cs in Api.SeaHavenIndustries/DTOs/
|
||||
3. ✓ Create EditEvent_DTO.cs in Api.SeaHavenIndustries/DTOs/
|
||||
4. ✓ Create CalendarController.cs in Api.SeaHavenIndustries/Controllers/
|
||||
5. ☐ Add DbSet<Events> to ApplicationDbContext.cs
|
||||
6. ☐ Run migrations (or execute SQL script)
|
||||
7. ☐ Test all endpoints using Postman/Swagger
|
||||
|
||||
FRONTEND SETUP (Next Step):
|
||||
---------------------------
|
||||
1. ☐ Create src/pages/calendar/CalendarPage.js (List view with FullCalendar)
|
||||
2. ☐ Create src/pages/calendar/EventFormPage.js (Create/Edit form)
|
||||
3. ☐ Create src/pages/calendar/api.js (API calls)
|
||||
4. ☐ Add routes to App.js
|
||||
5. ☐ Install FullCalendar: npm install @fullcalendar/react @fullcalendar/daygrid @fullcalendar/timegrid @fullcalendar/interaction
|
||||
|
||||
API ENDPOINTS SUMMARY:
|
||||
---------------------
|
||||
POST /api/Calendar/AddEvent - Create new event
|
||||
POST /api/Calendar/EditEvent - Update existing event
|
||||
GET /api/Calendar/GetEventById/{id} - Get single event by ID
|
||||
DELETE /api/Calendar/DeleteEvent/{id} - Delete event (soft delete)
|
||||
POST /api/Calendar/GetEventList - Get paginated list with search
|
||||
GET /api/Calendar/GetEventsForCalendar - Get events for calendar view (with date range)
|
||||
|
||||
KEY FEATURES IMPLEMENTED:
|
||||
-------------------------
|
||||
✓ CRUD operations (Create, Read, Update, Delete)
|
||||
✓ Pagination with search filter
|
||||
✓ Date range filtering for calendar view
|
||||
✓ Soft delete (IsDeleted flag)
|
||||
✓ Related record linking (RecordID, RecordName, RecordTargetType)
|
||||
✓ Color coding for calendar display
|
||||
✓ All-day event support
|
||||
✓ Reminder settings
|
||||
✓ Proper error handling
|
||||
✓ Consistent with existing controller patterns (AssetController)
|
||||
✓ Fixed pagination bug (totalCount calculated AFTER filters)
|
||||
|
||||
NOTES:
|
||||
------
|
||||
- EventColor values: 'primary', 'success', 'info', 'warning', 'danger'
|
||||
- ReminderMinutes: 0=Never, 5, 10, 15, 30, 60, 120, 240, 360, 720, 1440 (1 day)
|
||||
- StartTime/EndTime format: "08:00 AM", "02:30 PM", etc.
|
||||
- AllDay: When true, time fields can be null/ignored
|
||||
- RecordTargetType: Can be "Account", "Contact", "WorkOrder", "FollowUp", etc.
|
||||
|
||||
===================================================================================
|
||||
END OF FILE
|
||||
===================================================================================
|
||||
*/
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
using Api.SeaHavenIndustries.DTOs;
|
||||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SendGrid.Helpers.Mail;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
|
||||
namespace Api.SeaHavenIndustries.Controllers
|
||||
{
|
||||
|
|
@ -15,11 +17,17 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
public class ContactController : Controller
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly ApplicationDbContext _db;
|
||||
public ContactController(UserManager<ApplicationUser> userManager, ApplicationDbContext db)
|
||||
private readonly IContactService _contactService;
|
||||
private readonly ILocationService _locationService;
|
||||
|
||||
public ContactController(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IContactService contactService,
|
||||
ILocationService locationService)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_db = db;
|
||||
_contactService = contactService;
|
||||
_locationService = locationService;
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("AddContacts")]
|
||||
|
|
@ -27,32 +35,22 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
{
|
||||
try
|
||||
{
|
||||
Contacts model = new Contacts();
|
||||
model.Title = contacts.Title;
|
||||
model.Owner = contacts.Owner;
|
||||
model.FirstName = contacts.FirstName;
|
||||
model.MiddleName = contacts.MiddleName;
|
||||
model.LastName = contacts.LastName;
|
||||
model.ContactType = contacts.ContactType;
|
||||
model.PhoneNumber = contacts.PhoneNumber;
|
||||
model.Email = contacts.Email;
|
||||
model.Address1 = contacts.Address1;
|
||||
model.Address2 = contacts.Address2;
|
||||
model.City = contacts.City;
|
||||
model.State = contacts.State;
|
||||
model.Zip = contacts.Zip;
|
||||
model.FacebookUrl = contacts.FacebookUrl;
|
||||
model.LinkedInUrl = contacts.LinkedInUrl;
|
||||
model.TwitterUrl = contacts.TwitterUrl;
|
||||
model.LocationId = contacts.LocationId;
|
||||
model.AccountId = contacts.AccountId;
|
||||
_db.Contacts.Add(model);
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
|
||||
var userId = _userManager.GetUserId(User);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
await _contactService.CreateContactAsync(contacts.ToServiceCreateDTO(), userId);
|
||||
|
||||
return Ok(new DataResponse { Message = "Contact created successfully", Status = "200" });
|
||||
}
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -62,86 +60,66 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
{
|
||||
try
|
||||
{
|
||||
var exist = _db.Contacts.Where(w => w.Id == contacts.Id).FirstOrDefault();
|
||||
if (exist == null)
|
||||
return BadRequest(new Response { Status = "Error", Message = "Contact not found" });
|
||||
var userId = _userManager.GetUserId(User);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
exist.Title = contacts.Title;
|
||||
exist.Owner = contacts.Owner;
|
||||
exist.FirstName = contacts.FirstName;
|
||||
exist.MiddleName = contacts.MiddleName;
|
||||
exist.LastName = contacts.LastName;
|
||||
exist.ContactType = contacts.ContactType;
|
||||
exist.PhoneNumber = contacts.PhoneNumber;
|
||||
exist.Email = contacts.Email;
|
||||
exist.Address1 = contacts.Address1;
|
||||
exist.Address2 = contacts.Address2;
|
||||
exist.City = contacts.City;
|
||||
exist.State = contacts.State;
|
||||
exist.Zip = contacts.Zip;
|
||||
exist.FacebookUrl = contacts.FacebookUrl;
|
||||
exist.LinkedInUrl = contacts.LinkedInUrl;
|
||||
exist.TwitterUrl = contacts.TwitterUrl;
|
||||
exist.LocationId = contacts.LocationId;
|
||||
exist.AccountId = contacts.AccountId;
|
||||
_db.Contacts.Update(exist);
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
|
||||
await _contactService.UpdateContactAsync(contacts.Id, contacts.ToServiceUpdateDTO(), userId);
|
||||
|
||||
return Ok(new DataResponse { Message = "Contact updated successfully", Status = "200" });
|
||||
}
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Contact not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("DeleteContacts")]
|
||||
public async Task<IActionResult> DeleteContacts(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var exist = _db.Contacts.Where(w => w.Id == id).FirstOrDefault();
|
||||
if (exist != null)
|
||||
{
|
||||
_db.Remove(exist);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = "ID not matched!" });
|
||||
}
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok(new DataResponse { Message = "Removed Successfully", Status = "200" });
|
||||
var userId = _userManager.GetUserId(User);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
await _contactService.DeleteContactAsync(id, userId);
|
||||
|
||||
return Ok(new DataResponse { Message = "Contact deleted successfully", Status = "200" });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Contact not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("GetContactList")]
|
||||
public IActionResult GetContactList(string? search ="", int page = 1, int pageSize = 10)
|
||||
public async Task<IActionResult> GetContactList(string? search ="", int page = 1, int pageSize = 10)
|
||||
{
|
||||
var totalCount = _db.Contacts.Count();
|
||||
var data = _db.Contacts.Where(s=>
|
||||
(s.FirstName != null && s.FirstName.ToLower().Contains(search.ToLower())) ||
|
||||
(s.LastName != null && s.LastName.ToLower().Contains(search.ToLower())) ||
|
||||
(s.Email != null && s.Email.ToLower().Contains(search.ToLower())) ||
|
||||
(s.PhoneNumber != null && s.PhoneNumber.Contains(search.ToLower()))
|
||||
).Select(n => new
|
||||
{
|
||||
n.Id,
|
||||
Name = (n.FirstName + " " + n.LastName).Trim(),
|
||||
email = n.Email,
|
||||
phone = n.PhoneNumber
|
||||
}).OrderByDescending(d => d.Id).Skip((page - 1) * pageSize)
|
||||
.Take(pageSize).ToList();
|
||||
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
||||
var pagedResult = await _contactService.GetContactsPagedAsync(page, pageSize, search);
|
||||
var data = ContactListItem_DTO.FromServiceDTOs(pagedResult.Items);
|
||||
var totalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize);
|
||||
|
||||
var viewModel = new Pagination_DTO
|
||||
{
|
||||
Data = data,
|
||||
PageNumber = page,
|
||||
PageSize = pageSize,
|
||||
TotalCount = totalCount,
|
||||
TotalCount = pagedResult.TotalCount,
|
||||
TotalPages = totalPages
|
||||
};
|
||||
return Ok(viewModel);
|
||||
|
|
@ -151,18 +129,15 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
[Route("GetContacts")]
|
||||
public async Task<IActionResult> GetContacts()
|
||||
{
|
||||
var data = await _db.Contacts.ToListAsync();
|
||||
var data = await _contactService.GetAllContactsAsync();
|
||||
return Ok(data);
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("GetLocations")]
|
||||
public async Task<IActionResult> GetLocations()
|
||||
{
|
||||
var data = await _db.Locations.Select(s => new LocationViewModel
|
||||
{
|
||||
Id = s.Id,
|
||||
Name = s.Title + " - " + s.Name
|
||||
}).ToListAsync();
|
||||
var locations = await _locationService.GetAllLocationsAsync();
|
||||
var data = LocationViewModel_DTO.FromServiceDTOs(locations);
|
||||
return Ok(data);
|
||||
}
|
||||
|
||||
|
|
@ -170,33 +145,24 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
[Route("GetCategories")]
|
||||
public async Task<IActionResult> GetCategories()
|
||||
{
|
||||
var data = await _db.Categories.ToListAsync();
|
||||
return Ok(data);
|
||||
return StatusCode(501, new Response { Status = "Error", Message = "Not Implemented - Needs CategoryService" });
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("GetUsers")]
|
||||
public async Task<IActionResult> GetUsers()
|
||||
{
|
||||
var filteredUsers = await (from user in _db.Users
|
||||
where !(from userRole in _db.UserRoles
|
||||
join role in _db.Roles on userRole.RoleId equals role.Id
|
||||
where role.Name == "Admin" || role.Name == "Manager"
|
||||
select userRole.UserId).Contains(user.Id)
|
||||
select user).ToListAsync();
|
||||
return Ok(filteredUsers);
|
||||
return StatusCode(501, new Response { Status = "Error", Message = "Not Implemented - Needs UserService" });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("AddLocation")]
|
||||
public async Task<IActionResult> AddLocation(Location_DTO model)
|
||||
{
|
||||
try
|
||||
{
|
||||
Locations locations = new Locations();
|
||||
locations.Title = model.Title;
|
||||
locations.Name = model.Name;
|
||||
_db.Locations.Add(locations);
|
||||
await _db.SaveChangesAsync();
|
||||
var userId = _userManager.GetUserId(User) ?? "system";
|
||||
await _locationService.CreateLocationAsync(model.ToServiceCreateDTO(), userId);
|
||||
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -204,26 +170,21 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("EditLocation")]
|
||||
public async Task<IActionResult> EditLocation(EditLocation_DTO model)
|
||||
{
|
||||
try
|
||||
{
|
||||
var exist = _db.Locations.Where(w => w.Id == model.Id).FirstOrDefault();
|
||||
if (exist != null)
|
||||
{
|
||||
exist.Title = model.Title;
|
||||
exist.Name = model.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = "ID not matched!" });
|
||||
}
|
||||
_db.Locations.Update(exist);
|
||||
await _db.SaveChangesAsync();
|
||||
var userId = _userManager.GetUserId(User) ?? "system";
|
||||
await _locationService.UpdateLocationAsync(model.Id, model.ToServiceUpdateDTO(), userId);
|
||||
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = "ID not matched!" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
|
|
@ -236,20 +197,14 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
{
|
||||
try
|
||||
{
|
||||
var data1 = await _db.Templates.ToListAsync();
|
||||
_db.Templates.RemoveRange(data1);
|
||||
var data = await _db.Locations.Where(w => w.Id == Id).FirstOrDefaultAsync();
|
||||
if (data != null)
|
||||
{
|
||||
_db.Locations.Remove(data ?? new Locations());
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = "ID not matched!" });
|
||||
}
|
||||
await _db.SaveChangesAsync();
|
||||
var userId = _userManager.GetUserId(User) ?? "system";
|
||||
await _locationService.DeleteLocationAsync(Id, userId);
|
||||
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = "ID not matched!" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
|
|
@ -258,34 +213,22 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
|
||||
[HttpGet]
|
||||
[Route("GetAddressbook")]
|
||||
public IActionResult GetAddressbook(string? search = "", int page = 1, int pageSize = 10)
|
||||
public async Task<IActionResult> GetAddressbook(string? search = "", int page = 1, int pageSize = 10)
|
||||
{
|
||||
var totalCount = _db.Locations.Count();
|
||||
var data = _db.Locations.Where(w=> (w.Name ?? "").ToLower().Contains(search.ToLower()) || (w.Title ?? "").ToLower().Contains(search.ToLower())).Select(n => new
|
||||
{
|
||||
n.Id,
|
||||
n.Name,
|
||||
n.Title
|
||||
}).OrderByDescending(d => d.Id).Skip((page - 1) * pageSize)
|
||||
.Take(pageSize).ToList();
|
||||
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
||||
var pagedResult = await _locationService.GetLocationsPagedAsync(page, pageSize, search);
|
||||
var data = LocationListItem_DTO.FromServiceDTOs(pagedResult.Items);
|
||||
var totalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize);
|
||||
|
||||
var viewModel = new Pagination_DTO
|
||||
{
|
||||
Data = data,
|
||||
PageNumber = page,
|
||||
PageSize = pageSize,
|
||||
TotalCount = totalCount,
|
||||
TotalCount = pagedResult.TotalCount,
|
||||
TotalPages = totalPages
|
||||
};
|
||||
return Ok(viewModel);
|
||||
}
|
||||
|
||||
public class LocationViewModel
|
||||
{
|
||||
public int? Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ using Data.SeaHavenIndustries;
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
|
||||
namespace Api.SeaHavenIndustries.Controllers
|
||||
{
|
||||
|
|
@ -11,10 +13,12 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
[Route("api/dispatches")]
|
||||
public class DispatchController : Controller
|
||||
{
|
||||
private readonly ApplicationDbContext _db;
|
||||
private readonly IDispatchService _dispatchService;
|
||||
private readonly ApplicationDbContext _db; // Keep for complex queries
|
||||
|
||||
public DispatchController(ApplicationDbContext db)
|
||||
public DispatchController(IDispatchService dispatchService, ApplicationDbContext db)
|
||||
{
|
||||
_dispatchService = dispatchService;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
|
|
@ -327,10 +331,10 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
{
|
||||
dispatch.WorkOrder.Locations.Id,
|
||||
dispatch.WorkOrder.Locations.Name,
|
||||
dispatch.WorkOrder.Locations.Address,
|
||||
dispatch.WorkOrder.Locations.Address1,
|
||||
dispatch.WorkOrder.Locations.City,
|
||||
dispatch.WorkOrder.Locations.State,
|
||||
dispatch.WorkOrder.Locations.ZipCode
|
||||
dispatch.WorkOrder.Locations.Zip
|
||||
},
|
||||
WorkOrders = linkedWOs,
|
||||
Checklist = checklist,
|
||||
|
|
|
|||
|
|
@ -1,176 +1,180 @@
|
|||
using Api.SeaHavenIndustries.DTOs;
|
||||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using System.Security.Claims;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/Employee")]
|
||||
[Route("api/employees")]
|
||||
public class EmployeeController : Controller
|
||||
{
|
||||
private readonly ApplicationDbContext _db;
|
||||
private readonly IEmployeeService _employeeService;
|
||||
|
||||
public EmployeeController(ApplicationDbContext db)
|
||||
public EmployeeController(IEmployeeService employeeService)
|
||||
{
|
||||
_db = db;
|
||||
_employeeService = employeeService;
|
||||
}
|
||||
|
||||
// -----------------------------------
|
||||
// POST: api/Employee/AddEmployees
|
||||
// POST: api/Employee/AddEmployees or /Create
|
||||
// -----------------------------------
|
||||
[HttpPost("AddEmployees")]
|
||||
[HttpPost("Create")]
|
||||
public async Task<IActionResult> AddEmployees(AddEmployee_DTO dto)
|
||||
{
|
||||
var employee = new Employee
|
||||
try
|
||||
{
|
||||
FirstName = dto.FirstName,
|
||||
LastName = dto.LastName,
|
||||
JobTitleId = dto.JobTitleId,
|
||||
PermissionGroupId = dto.PermissionGroupId,
|
||||
Status = dto.Status,
|
||||
LastLogin = dto.LastLogin,
|
||||
CreatedDate = DateTime.UtcNow
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(dto.Email))
|
||||
{
|
||||
employee.Emails.Add(new EmployeeEmail
|
||||
var createDto = new CreateEmployeeDTO
|
||||
{
|
||||
Address = dto.Email,
|
||||
IsPrimary = true,
|
||||
EmailTypeId = 1
|
||||
});
|
||||
}
|
||||
FirstName = dto.FirstName,
|
||||
LastName = dto.LastName,
|
||||
JobTitleId = dto.JobTitleId,
|
||||
DepartmentId = null, // Map from PermissionGroupId if needed
|
||||
Email = dto.Email,
|
||||
Phone = dto.Phone
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(dto.Phone))
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
await _employeeService.CreateEmployeeAsync(createDto, userId);
|
||||
|
||||
return Ok(new { Message = "Employee added successfully" });
|
||||
}
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
employee.Phones.Add(new EmployeePhone
|
||||
{
|
||||
Number = dto.Phone,
|
||||
Extension = dto.PhoneExtension,
|
||||
IsPrimary = true,
|
||||
PhoneTypeId = 1
|
||||
});
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
|
||||
_db.Employees.Add(employee);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return Ok(new { Message = "Employee added successfully" });
|
||||
}
|
||||
|
||||
// -----------------------------------
|
||||
// POST: api/Employee/EditEmployees
|
||||
// POST: api/Employee/EditEmployees or /Update
|
||||
// -----------------------------------
|
||||
[HttpPost("EditEmployees")]
|
||||
[HttpPost("Update")]
|
||||
public async Task<IActionResult> EditEmployees(EditEmployee_DTO dto)
|
||||
{
|
||||
var employee = await _db.Employees
|
||||
.Include(e => e.Emails)
|
||||
.Include(e => e.Phones)
|
||||
.FirstOrDefaultAsync(e => e.Id == dto.Id);
|
||||
try
|
||||
{
|
||||
var updateDto = new UpdateEmployeeDTO
|
||||
{
|
||||
FirstName = dto.FirstName,
|
||||
LastName = dto.LastName,
|
||||
JobTitleId = dto.JobTitleId
|
||||
};
|
||||
|
||||
if (employee == null)
|
||||
return BadRequest("Employee not found");
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
employee.FirstName = dto.FirstName;
|
||||
employee.LastName = dto.LastName;
|
||||
employee.JobTitleId = dto.JobTitleId;
|
||||
employee.PermissionGroupId = dto.PermissionGroupId;
|
||||
employee.Status = dto.Status;
|
||||
employee.LastLogin = dto.LastLogin;
|
||||
employee.LastModificationTime = DateTime.UtcNow;
|
||||
await _employeeService.UpdateEmployeeAsync(dto.Id, updateDto, userId);
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok(new { Message = "Employee updated successfully" });
|
||||
return Ok(new { Message = "Employee updated successfully" });
|
||||
}
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Employee not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------
|
||||
// GET: api/Employee/GetEmployeeList
|
||||
// -----------------------------------
|
||||
[HttpGet("GetEmployeeList")]
|
||||
public IActionResult GetEmployeeList(string? search = "", int page = 1, int pageSize = 12)
|
||||
public async Task<IActionResult> GetEmployeeList(string? search = "", int page = 1, int pageSize = 12)
|
||||
{
|
||||
search ??= "";
|
||||
var pagedResult = await _employeeService.GetEmployeesPagedAsync(page, pageSize, search);
|
||||
|
||||
var totalCount = _db.Employees.Where(e => !e.IsDeleted.HasValue || e.IsDeleted == false).Count();
|
||||
var data = pagedResult.Items.Select(e => new
|
||||
{
|
||||
id = e.Id,
|
||||
firstName = e.FirstName,
|
||||
lastName = e.LastName,
|
||||
jobTitle = e.JobTitleName ?? "",
|
||||
email = e.Email ?? "",
|
||||
phone = e.Phone ?? "",
|
||||
department = e.DepartmentName ?? "",
|
||||
status = "Active" // Map from employee status if available
|
||||
});
|
||||
|
||||
var data = _db.Employees
|
||||
.Include(e => e.JobTitle)
|
||||
.Include(e => e.Department)
|
||||
.Include(e => e.Emails)
|
||||
.Include(e => e.Phones)
|
||||
.Where(e => !e.IsDeleted.HasValue || e.IsDeleted == false)
|
||||
.Where(e =>
|
||||
string.IsNullOrWhiteSpace(search) ||
|
||||
(e.FirstName != null && e.FirstName.ToLower().Contains(search.ToLower())) ||
|
||||
(e.LastName != null && e.LastName.ToLower().Contains(search.ToLower())) ||
|
||||
e.Emails.Any(x => x.Address != null && x.Address.ToLower().Contains(search.ToLower())) ||
|
||||
e.Phones.Any(x => x.Number != null && x.Number.Contains(search))
|
||||
)
|
||||
.Select(e => new
|
||||
{
|
||||
e.Id,
|
||||
e.FirstName,
|
||||
e.LastName,
|
||||
JobTitleName = e.JobTitle != null ? e.JobTitle.Name : "",
|
||||
DepartmentName = e.Department != null ? e.Department.Name : "",
|
||||
e.Status,
|
||||
PrimaryEmail = e.Emails.Where(x => x.IsPrimary == true).Select(x => x.Address).FirstOrDefault(),
|
||||
PrimaryPhone = e.Phones.Where(x => x.IsPrimary == true).Select(x => x.Number).FirstOrDefault(),
|
||||
PhoneExtension = e.Phones.Where(x => x.IsPrimary == true).Select(x => x.Extension).FirstOrDefault()
|
||||
})
|
||||
.OrderByDescending(e => e.Id)
|
||||
.ToList()
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(e => new
|
||||
{
|
||||
id = e.Id,
|
||||
firstName = e.FirstName,
|
||||
lastName = e.LastName,
|
||||
jobTitle = e.JobTitleName ?? "",
|
||||
email = e.PrimaryEmail ?? "",
|
||||
phone = e.PrimaryPhone != null
|
||||
? e.PrimaryPhone + (e.PhoneExtension != null ? " ext. " + e.PhoneExtension : "")
|
||||
: "",
|
||||
department = e.DepartmentName ?? "",
|
||||
status = e.Status
|
||||
});
|
||||
|
||||
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
||||
var totalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize);
|
||||
|
||||
var viewModel = new Pagination_DTO
|
||||
{
|
||||
Data = data,
|
||||
PageNumber = page,
|
||||
PageSize = pageSize,
|
||||
TotalCount = totalCount,
|
||||
TotalCount = pagedResult.TotalCount,
|
||||
TotalPages = totalPages
|
||||
};
|
||||
|
||||
return Ok(viewModel);
|
||||
}
|
||||
// -----------------------------------
|
||||
// GET: api/Employee/DeleteEmployees?id=5
|
||||
// GET: api/Employee/GetById?id=5
|
||||
// -----------------------------------
|
||||
[HttpGet("GetById")]
|
||||
public async Task<IActionResult> GetById(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var employee = await _employeeService.GetEmployeeByIdAsync(id);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
id = employee.Id,
|
||||
firstName = employee.FirstName,
|
||||
lastName = employee.LastName,
|
||||
jobTitle = employee.JobTitleName ?? "",
|
||||
email = employee.Email ?? "",
|
||||
phone = employee.Phone ?? "",
|
||||
department = employee.DepartmentName ?? "",
|
||||
status = "Active"
|
||||
});
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Employee not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------
|
||||
// GET: api/Employee/DeleteEmployees or /Delete?id=5
|
||||
// -----------------------------------
|
||||
[HttpGet("DeleteEmployees")]
|
||||
[HttpPost("Delete")]
|
||||
public async Task<IActionResult> DeleteEmployees(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var employee = await _db.Employees
|
||||
.FirstOrDefaultAsync(e => e.Id == id);
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
if (employee == null)
|
||||
return BadRequest(new Response
|
||||
{
|
||||
Status = "Error",
|
||||
Message = "Employee not found."
|
||||
});
|
||||
|
||||
_db.Employees.Remove(employee);
|
||||
await _db.SaveChangesAsync();
|
||||
await _employeeService.DeleteEmployeeAsync(id, userId);
|
||||
|
||||
return Ok(new DataResponse
|
||||
{
|
||||
|
|
@ -178,7 +182,7 @@ public class EmployeeController : Controller
|
|||
Status = "200"
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return BadRequest(new Response
|
||||
{
|
||||
|
|
@ -186,6 +190,31 @@ public class EmployeeController : Controller
|
|||
Message = ex.Message
|
||||
});
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Employee not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------
|
||||
// GET: api/Employee/Dropdown
|
||||
// -----------------------------------
|
||||
[HttpGet("Dropdown")]
|
||||
public async Task<IActionResult> GetDropdown()
|
||||
{
|
||||
var pagedResult = await _employeeService.GetEmployeesPagedAsync(1, 1000, "");
|
||||
|
||||
var data = pagedResult.Items.Select(e => new
|
||||
{
|
||||
id = e.Id,
|
||||
name = $"{e.FirstName} {e.LastName}".Trim()
|
||||
});
|
||||
|
||||
return Ok(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
using System.Security.Claims;
|
||||
using Api.SeaHavenIndustries.DTOs;
|
||||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
|
|
@ -12,11 +16,15 @@ using Microsoft.EntityFrameworkCore;
|
|||
[Route("api/followups")]
|
||||
public class FollowUpController : ControllerBase
|
||||
{
|
||||
private readonly ApplicationDbContext _db;
|
||||
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(ApplicationDbContext db, UserManager<ApplicationUser> users)
|
||||
public FollowUpController(IFollowUpService followUpService, IFollowUpDataService followUpDataService, ApplicationDbContext db, UserManager<ApplicationUser> users)
|
||||
{
|
||||
_followUpService = followUpService;
|
||||
_followUpDataService = followUpDataService;
|
||||
_db = db;
|
||||
_users = users;
|
||||
}
|
||||
|
|
@ -30,55 +38,28 @@ public class FollowUpController : ControllerBase
|
|||
{
|
||||
status = string.IsNullOrWhiteSpace(status) ? "Open" : status;
|
||||
|
||||
var query = _db.FollowUps
|
||||
.Include(x => x.Employee)
|
||||
.Include(x => x.Account)
|
||||
.Include(x => x.Location)
|
||||
.Include(x => x.WorkOrder)
|
||||
.Where(x => x.Status == status);
|
||||
var pagedResult = await _followUpService.GetFollowUpsPagedAsync(page, pageSize, search, status);
|
||||
var items = pagedResult.Items;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
var rows = items.Select(x => new
|
||||
{
|
||||
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))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var rows = await query
|
||||
.OrderByDescending(x => x.Id)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
x.Reason,
|
||||
x.ScheduleDate,
|
||||
x.ScheduleStartTime,
|
||||
x.EmployeeId,
|
||||
x.WorkOrderId,
|
||||
x.AccountId,
|
||||
x.LocationId,
|
||||
AccountName = x.Account != null ? x.Account.Name : "",
|
||||
AssignedTo = x.Employee != null
|
||||
? (x.Employee.FirstName + " " + x.Employee.LastName)
|
||||
: "",
|
||||
LocationName = x.Location != null ? x.Location.Name : "",
|
||||
WorkOrderNumber = x.WorkOrder != null ? x.WorkOrder.WorkerOrderNumber : "",
|
||||
InternalWONumber = x.WorkOrder != null ? x.WorkOrder.InternalWONumber : "",
|
||||
x.Status,
|
||||
x.CreatedDate,
|
||||
CreatedById = x.createdby
|
||||
})
|
||||
.ToListAsync();
|
||||
Id = x.Id,
|
||||
Reason = x.Reason,
|
||||
ScheduleDate = x.ScheduleDate,
|
||||
ScheduleStartTime = x.ScheduleStartTime,
|
||||
EmployeeId = x.EmployeeId,
|
||||
WorkOrderId = x.WorkOrderId,
|
||||
AccountId = x.AccountId,
|
||||
LocationId = x.LocationId,
|
||||
AccountName = x.AccountName ?? "",
|
||||
AssignedTo = x.EmployeeName ?? "",
|
||||
LocationName = x.LocationName ?? "",
|
||||
WorkOrderNumber = "",
|
||||
InternalWONumber = "",
|
||||
Status = x.Status,
|
||||
CreatedDate = x.CreatedDate,
|
||||
CreatedById = (string?)null
|
||||
}).ToList();
|
||||
|
||||
var creatorIds = rows
|
||||
.Select(r => r.CreatedById)
|
||||
|
|
@ -124,20 +105,15 @@ public class FollowUpController : ControllerBase
|
|||
Data = data,
|
||||
PageNumber = page,
|
||||
PageSize = pageSize,
|
||||
TotalCount = totalCount,
|
||||
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
|
||||
TotalCount = pagedResult.TotalCount,
|
||||
TotalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize)
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("GetById")]
|
||||
public async Task<IActionResult> GetById(int id)
|
||||
{
|
||||
var model = await _db.FollowUps
|
||||
.Include(x => x.Employee)
|
||||
.Include(x => x.Account)
|
||||
.Include(x => x.Location)
|
||||
.Include(x => x.WorkOrder)
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
var model = await _followUpDataService.GetByIdWithDetailsAsync(id);
|
||||
|
||||
if (model == null) return NotFound();
|
||||
|
||||
|
|
@ -182,56 +158,86 @@ public class FollowUpController : ControllerBase
|
|||
[HttpPost("Create")]
|
||||
public async Task<IActionResult> Create(FollowUp_DTO dto)
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
|
||||
var model = new FollowUps
|
||||
try
|
||||
{
|
||||
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
|
||||
};
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
_db.FollowUps.Add(model);
|
||||
await _db.SaveChangesAsync();
|
||||
var model = 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",
|
||||
CreatedDate = DateTime.UtcNow,
|
||||
createdby = userId
|
||||
};
|
||||
|
||||
return Ok(new DataResponse { Status = "200", Message = "Follow Up created", Data = model.Id });
|
||||
await _followUpDataService.AddAsync(model);
|
||||
|
||||
return Ok(new DataResponse { Status = "200", Message = "Follow Up created", Data = model.Id });
|
||||
}
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("Update")]
|
||||
public async Task<IActionResult> Update(FollowUp_DTO dto)
|
||||
{
|
||||
var model = await _db.FollowUps.FindAsync(dto.Id);
|
||||
if (model == null) return NotFound();
|
||||
try
|
||||
{
|
||||
var model = await _followUpDataService.GetByIdAsync(dto.Id);
|
||||
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;
|
||||
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 _db.SaveChangesAsync();
|
||||
return Ok(new DataResponse { Status = "200", Message = "Follow Up updated" });
|
||||
await _followUpDataService.UpdateAsync(model);
|
||||
return Ok(new DataResponse { Status = "200", Message = "Follow Up updated" });
|
||||
}
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Follow Up not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("Complete")]
|
||||
public async Task<IActionResult> Complete(int id)
|
||||
{
|
||||
var model = await _db.FollowUps.FindAsync(id);
|
||||
var model = await _followUpDataService.GetByIdAsync(id);
|
||||
if (model == null) return NotFound();
|
||||
|
||||
model.Status = "Completed";
|
||||
model.LastModificationTime = DateTime.UtcNow;
|
||||
await _db.SaveChangesAsync();
|
||||
await _followUpDataService.UpdateAsync(model);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
|
@ -239,12 +245,12 @@ public class FollowUpController : ControllerBase
|
|||
[HttpPost("Cancel")]
|
||||
public async Task<IActionResult> Cancel(int id)
|
||||
{
|
||||
var model = await _db.FollowUps.FindAsync(id);
|
||||
var model = await _followUpDataService.GetByIdAsync(id);
|
||||
if (model == null) return NotFound();
|
||||
|
||||
model.Status = "Cancelled";
|
||||
model.LastModificationTime = DateTime.UtcNow;
|
||||
await _db.SaveChangesAsync();
|
||||
await _followUpDataService.UpdateAsync(model);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
|
@ -252,12 +258,23 @@ public class FollowUpController : ControllerBase
|
|||
[HttpPost("Delete")]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
var model = await _db.FollowUps.FindAsync(id);
|
||||
if (model == null) return NotFound();
|
||||
try
|
||||
{
|
||||
var model = await _followUpDataService.GetByIdAsync(id);
|
||||
if (model == null)
|
||||
return NotFound(new Response { Status = "Error", Message = "Follow Up not found" });
|
||||
|
||||
_db.FollowUps.Remove(model);
|
||||
await _db.SaveChangesAsync();
|
||||
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)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Follow Up not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
using Api.SeaHavenIndustries.DTOs;
|
||||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Api.SeaHavenIndustries.Controllers
|
||||
{
|
||||
|
|
@ -12,58 +17,44 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
[Route("api/locations")]
|
||||
public class LocationController : Controller
|
||||
{
|
||||
private readonly ApplicationDbContext _db;
|
||||
private readonly ILocationService _locationService;
|
||||
private readonly ILocationDataService _locationDataService; // Keep for direct entity operations
|
||||
|
||||
public LocationController(ApplicationDbContext db)
|
||||
public LocationController(ILocationService locationService, ILocationDataService locationDataService)
|
||||
{
|
||||
_db = db;
|
||||
_locationService = locationService;
|
||||
_locationDataService = locationDataService;
|
||||
}
|
||||
|
||||
[HttpGet("GetLocationList")]
|
||||
public IActionResult GetLocationList(string? search = "", int page = 1, int pageSize = 10)
|
||||
public async Task<IActionResult> GetLocationList(string? search = "", int page = 1, int pageSize = 10)
|
||||
{
|
||||
var query = _db.Locations.AsQueryable();
|
||||
var pagedResult = await _locationService.GetLocationsPagedAsync(page, pageSize, search);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
var data = pagedResult.Items.Select(l => new
|
||||
{
|
||||
var s = search.ToLower();
|
||||
query = query.Where(l =>
|
||||
(l.Name ?? "").ToLower().Contains(s) ||
|
||||
(l.Title ?? "").ToLower().Contains(s) ||
|
||||
(l.Address ?? "").ToLower().Contains(s) ||
|
||||
(l.City ?? "").ToLower().Contains(s));
|
||||
}
|
||||
|
||||
var totalCount = query.Count();
|
||||
|
||||
var data = query
|
||||
.OrderByDescending(l => l.Id)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(l => new
|
||||
{
|
||||
l.Id,
|
||||
l.Name,
|
||||
l.Title,
|
||||
l.Address,
|
||||
l.City,
|
||||
l.State,
|
||||
l.ZipCode,
|
||||
l.Phone,
|
||||
l.Contact,
|
||||
l.ContactEmail,
|
||||
l.Status,
|
||||
l.AccountId
|
||||
})
|
||||
.ToList();
|
||||
Id = l.Id,
|
||||
Name = l.Name,
|
||||
Title = l.Title,
|
||||
Address = l.Address1,
|
||||
Address1 = l.Address1,
|
||||
Address2 = l.Address2,
|
||||
City = l.City,
|
||||
State = l.State,
|
||||
ZipCode = l.Zip,
|
||||
Phone = l.PhoneNumber,
|
||||
Contact = (string?)null,
|
||||
ContactEmail = l.Email,
|
||||
Status = l.Status
|
||||
});
|
||||
|
||||
var viewModel = new Pagination_DTO
|
||||
{
|
||||
Data = data,
|
||||
PageNumber = page,
|
||||
PageSize = pageSize,
|
||||
TotalCount = totalCount,
|
||||
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
|
||||
TotalCount = pagedResult.TotalCount,
|
||||
TotalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize)
|
||||
};
|
||||
|
||||
return Ok(viewModel);
|
||||
|
|
@ -72,29 +63,29 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetLocationById(int id)
|
||||
{
|
||||
var location = await _db.Locations
|
||||
.Where(l => l.Id == id)
|
||||
.Select(l => new
|
||||
{
|
||||
l.Id,
|
||||
l.Name,
|
||||
l.Title,
|
||||
l.Address,
|
||||
l.City,
|
||||
l.State,
|
||||
l.ZipCode,
|
||||
l.Phone,
|
||||
l.Contact,
|
||||
l.ContactEmail,
|
||||
l.Status,
|
||||
l.AccountId
|
||||
})
|
||||
.FirstOrDefaultAsync();
|
||||
var location = await _locationDataService.GetByIdAsync(id);
|
||||
|
||||
if (location == null)
|
||||
return NotFound(new Response { Status = "Error", Message = "Location not found" });
|
||||
|
||||
return Ok(location);
|
||||
var result = new
|
||||
{
|
||||
location.Id,
|
||||
location.Name,
|
||||
location.Title,
|
||||
Address = location.Address1,
|
||||
location.Address1,
|
||||
location.Address2,
|
||||
location.City,
|
||||
location.State,
|
||||
ZipCode = location.Zip,
|
||||
Phone = location.PhoneNumber,
|
||||
Contact = (string?)null,
|
||||
ContactEmail = location.Email,
|
||||
location.Status
|
||||
};
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
|
|
@ -106,24 +97,27 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
{
|
||||
Name = model.Name,
|
||||
Title = model.Title,
|
||||
Address = model.Address,
|
||||
Address1 = model.Address,
|
||||
City = model.City,
|
||||
State = model.State,
|
||||
ZipCode = model.ZipCode,
|
||||
Phone = model.Phone,
|
||||
Contact = model.Contact,
|
||||
ContactEmail = model.ContactEmail,
|
||||
Zip = model.ZipCode,
|
||||
PhoneNumber = model.Phone,
|
||||
Email = model.ContactEmail,
|
||||
Status = model.Status,
|
||||
AccountId = model.GetAccountId()
|
||||
CreatedDate = DateTime.Now
|
||||
};
|
||||
|
||||
_db.Locations.Add(location);
|
||||
await _db.SaveChangesAsync();
|
||||
await _locationDataService.AddAsync(location);
|
||||
return Ok(new DataResponse { Message = "Location Created Successfully", Status = "200" });
|
||||
}
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,28 +126,36 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
{
|
||||
try
|
||||
{
|
||||
var exist = await _db.Locations.Where(l => l.Id == id).FirstOrDefaultAsync();
|
||||
var exist = await _locationDataService.GetByIdAsync(id);
|
||||
if (exist == null)
|
||||
return BadRequest(new Response { Status = "Error", Message = "Location not found" });
|
||||
return NotFound(new Response { Status = "Error", Message = "Location not found" });
|
||||
|
||||
exist.Name = model.Name;
|
||||
exist.Title = model.Title;
|
||||
exist.Address = model.Address;
|
||||
exist.Address1 = model.Address;
|
||||
exist.City = model.City;
|
||||
exist.State = model.State;
|
||||
exist.ZipCode = model.ZipCode;
|
||||
exist.Phone = model.Phone;
|
||||
exist.Contact = model.Contact;
|
||||
exist.ContactEmail = model.ContactEmail;
|
||||
exist.Zip = model.ZipCode;
|
||||
exist.PhoneNumber = model.Phone;
|
||||
exist.Email = model.ContactEmail;
|
||||
exist.Status = model.Status;
|
||||
exist.AccountId = model.GetAccountId();
|
||||
exist.LastModificationTime = DateTime.Now;
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
await _locationDataService.UpdateAsync(exist);
|
||||
return Ok(new DataResponse { Message = "Location Updated Successfully", Status = "200" });
|
||||
}
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Location not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -162,17 +164,20 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
{
|
||||
try
|
||||
{
|
||||
var exist = await _db.Locations.Where(l => l.Id == id).FirstOrDefaultAsync();
|
||||
var exist = await _locationDataService.GetByIdAsync(id);
|
||||
if (exist == null)
|
||||
return BadRequest(new Response { Status = "Error", Message = "Location not found" });
|
||||
return NotFound(new Response { Status = "Error", Message = "Location not found" });
|
||||
|
||||
_db.Locations.Remove(exist);
|
||||
await _db.SaveChangesAsync();
|
||||
await _locationDataService.DeleteAsync(id);
|
||||
return Ok(new DataResponse { Message = "Location Deleted Successfully", Status = "200" });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Location not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,70 +1,147 @@
|
|||
using Api.SeaHavenIndustries.DTOs;
|
||||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using System.Security.Claims;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/PMSchedule")]
|
||||
[Route("api/pmschedules")]
|
||||
public class PMScheduleController : ControllerBase
|
||||
{
|
||||
private readonly ApplicationDbContext _db;
|
||||
private readonly IPMScheduleService _pmScheduleService;
|
||||
|
||||
public PMScheduleController(ApplicationDbContext db)
|
||||
public PMScheduleController(IPMScheduleService pmScheduleService)
|
||||
{
|
||||
_db = db;
|
||||
_pmScheduleService = pmScheduleService;
|
||||
}
|
||||
|
||||
[HttpGet("GetList")]
|
||||
public IActionResult GetList()
|
||||
public async Task<IActionResult> GetList()
|
||||
{
|
||||
var data = _db.PMSchedules
|
||||
.Include(x => x.Asset)
|
||||
.Include(x => x.Location)
|
||||
.OrderByDescending(x => x.Id)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Id,
|
||||
x.Name,
|
||||
x.Frequency,
|
||||
x.StartDate,
|
||||
x.IsActive,
|
||||
Asset = x.Asset != null ? x.Asset.Name : null,
|
||||
Location = x.Location != null ? x.Location.Name : null
|
||||
})
|
||||
.ToList();
|
||||
var items = await _pmScheduleService.GetAllPMSchedulesAsync();
|
||||
|
||||
var data = items.Select(x => new
|
||||
{
|
||||
Id = x.Id,
|
||||
Name = x.Name,
|
||||
Frequency = x.Frequency,
|
||||
StartDate = x.StartDate,
|
||||
IsActive = x.IsActive,
|
||||
Asset = x.AssetName,
|
||||
Location = x.LocationName
|
||||
});
|
||||
|
||||
return Ok(data);
|
||||
}
|
||||
|
||||
[HttpGet("GetById")]
|
||||
public async Task<IActionResult> GetById([FromQuery] int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pmSchedule = await _pmScheduleService.GetPMScheduleByIdAsync(id);
|
||||
if (pmSchedule == null)
|
||||
return NotFound(new Response { Status = "Error", Message = "PM Schedule not found" });
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
Id = pmSchedule.Id,
|
||||
Name = pmSchedule.Name,
|
||||
Frequency = pmSchedule.Frequency,
|
||||
StartDate = pmSchedule.StartDate,
|
||||
IsActive = pmSchedule.IsActive,
|
||||
AssetId = pmSchedule.AssetId,
|
||||
LocationId = pmSchedule.LocationId,
|
||||
Asset = pmSchedule.AssetName,
|
||||
Location = pmSchedule.LocationName
|
||||
});
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "PM Schedule not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("Save")]
|
||||
public async Task<IActionResult> Save(PMSchedule_DTO dto)
|
||||
{
|
||||
PMSchedules model;
|
||||
|
||||
if (dto.Id == 0)
|
||||
try
|
||||
{
|
||||
model = new PMSchedules
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
if (dto.Id == 0)
|
||||
{
|
||||
CreatedDate = DateTime.UtcNow
|
||||
};
|
||||
_db.PMSchedules.Add(model);
|
||||
var createDto = new CreatePMScheduleDTO
|
||||
{
|
||||
Name = dto.Name,
|
||||
Frequency = dto.Frequency,
|
||||
StartDate = dto.StartDate,
|
||||
IsActive = dto.IsActive,
|
||||
AssetId = dto.AssetId,
|
||||
LocationId = dto.LocationId
|
||||
};
|
||||
await _pmScheduleService.CreatePMScheduleAsync(createDto, userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var updateDto = new UpdatePMScheduleDTO
|
||||
{
|
||||
Name = dto.Name,
|
||||
Frequency = dto.Frequency,
|
||||
StartDate = dto.StartDate,
|
||||
IsActive = dto.IsActive,
|
||||
AssetId = dto.AssetId,
|
||||
LocationId = dto.LocationId
|
||||
};
|
||||
await _pmScheduleService.UpdatePMScheduleAsync(dto.Id, updateDto, userId);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
else
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
model = await _db.PMSchedules.FindAsync(dto.Id);
|
||||
if (model == null) return NotFound();
|
||||
model.LastModificationTime = DateTime.UtcNow;
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "PM Schedule not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
model.Name = dto.Name;
|
||||
model.Frequency = dto.Frequency;
|
||||
model.StartDate = dto.StartDate;
|
||||
model.IsActive = dto.IsActive;
|
||||
model.AssetId = dto.AssetId;
|
||||
model.LocationId = dto.LocationId;
|
||||
[HttpPost("Delete")]
|
||||
public async Task<IActionResult> Delete([FromQuery] int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok();
|
||||
await _pmScheduleService.DeletePMScheduleAsync(id, userId);
|
||||
return Ok(new DataResponse { Message = "PM Schedule deleted successfully", Status = "200" });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "PM Schedule not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -375,7 +375,7 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
{
|
||||
Name = matchCode,
|
||||
Title = building,
|
||||
Address = address,
|
||||
Address1 = address,
|
||||
Status = "Active"
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
using Api.SeaHavenIndustries.DTOs;
|
||||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Api.SeaHavenIndustries.Controllers
|
||||
{
|
||||
|
|
@ -12,13 +16,15 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
[Route("api/vendors")]
|
||||
public class VendorController : Controller
|
||||
{
|
||||
private readonly ApplicationDbContext _db;
|
||||
private readonly IVendorService _vendorService;
|
||||
private readonly ApplicationDbContext _db; // Keep for complex queries like nearby vendors
|
||||
private readonly Helper.ZipCodeDistance _zipDistance;
|
||||
private readonly Helper.VendorPortalTokenService _vendorTokens;
|
||||
private readonly IConfiguration _config;
|
||||
|
||||
public VendorController(ApplicationDbContext db, Helper.ZipCodeDistance zipDistance, Helper.VendorPortalTokenService vendorTokens, IConfiguration config)
|
||||
public VendorController(IVendorService vendorService, ApplicationDbContext db, Helper.ZipCodeDistance zipDistance, Helper.VendorPortalTokenService vendorTokens, IConfiguration config)
|
||||
{
|
||||
_vendorService = vendorService;
|
||||
_db = db;
|
||||
_zipDistance = zipDistance;
|
||||
_vendorTokens = vendorTokens;
|
||||
|
|
@ -26,64 +32,36 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
}
|
||||
|
||||
[HttpGet("GetVendorList")]
|
||||
public IActionResult GetVendorList(string? search = "", int page = 1, int pageSize = 10)
|
||||
public async Task<IActionResult> GetVendorList(string? search = "", int page = 1, int pageSize = 10)
|
||||
{
|
||||
var query = _db.Vendors.Where(v => v.IsActive);
|
||||
var pagedResult = await _vendorService.GetVendorsPagedAsync(page, pageSize, search);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
var data = pagedResult.Items.Select(v => new
|
||||
{
|
||||
var s = search.ToLower();
|
||||
query = query.Where(v =>
|
||||
(v.CompanyName ?? "").ToLower().Contains(s) ||
|
||||
(v.ContactName ?? "").ToLower().Contains(s) ||
|
||||
(v.Email ?? "").ToLower().Contains(s) ||
|
||||
(v.TradeSpecialties ?? "").ToLower().Contains(s));
|
||||
}
|
||||
|
||||
var totalCount = query.Count();
|
||||
|
||||
var data = query.OrderByDescending(v => v.Id)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(v => new
|
||||
{
|
||||
v.Id,
|
||||
v.CompanyName,
|
||||
v.ContactName,
|
||||
v.Email,
|
||||
v.Phone,
|
||||
v.TradeSpecialties,
|
||||
v.IsActive
|
||||
})
|
||||
.ToList();
|
||||
v.Id,
|
||||
CompanyName = v.Name,
|
||||
v.Email,
|
||||
v.Phone
|
||||
});
|
||||
|
||||
return Ok(new Pagination_DTO
|
||||
{
|
||||
Data = data,
|
||||
PageNumber = page,
|
||||
PageSize = pageSize,
|
||||
TotalCount = totalCount,
|
||||
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
|
||||
TotalCount = pagedResult.TotalCount,
|
||||
TotalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize)
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetVendorById(int id)
|
||||
[HttpGet("GetById")]
|
||||
public async Task<IActionResult> GetVendorById([FromQuery] int? id)
|
||||
{
|
||||
var vendor = await _db.Vendors.Where(v => v.Id == id).Select(v => new
|
||||
{
|
||||
v.Id,
|
||||
v.CompanyName,
|
||||
v.ContactName,
|
||||
v.Email,
|
||||
v.Phone,
|
||||
v.Address,
|
||||
v.City,
|
||||
v.State,
|
||||
v.Zip,
|
||||
v.TradeSpecialties,
|
||||
v.IsActive
|
||||
}).FirstOrDefaultAsync();
|
||||
if (id == null)
|
||||
return BadRequest(new Response { Status = "Error", Message = "Id is required" });
|
||||
|
||||
var vendor = await _vendorService.GetVendorByIdAsync(id.Value);
|
||||
|
||||
if (vendor == null)
|
||||
return NotFound(new Response { Status = "Error", Message = "Vendor not found" });
|
||||
|
|
@ -92,72 +70,98 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
}
|
||||
|
||||
[HttpPost]
|
||||
[HttpPost("Create")]
|
||||
public async Task<IActionResult> Create([FromBody] Vendor_DTO model)
|
||||
{
|
||||
try
|
||||
{
|
||||
var vendor = new Vendor
|
||||
var createDto = new CreateVendorDTO
|
||||
{
|
||||
CompanyName = model.CompanyName,
|
||||
ContactName = model.ContactName,
|
||||
Email = model.Email,
|
||||
Phone = model.Phone,
|
||||
Address = model.Address,
|
||||
City = model.City,
|
||||
State = model.State,
|
||||
Zip = model.Zip,
|
||||
TradeSpecialties = model.TradeSpecialties,
|
||||
IsActive = model.IsActive
|
||||
Name = model.CompanyName ?? throw new ArgumentException("CompanyName is required")
|
||||
};
|
||||
_db.Vendors.Add(vendor);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
await _vendorService.CreateVendorAsync(createDto, userId);
|
||||
return Ok(new DataResponse { Message = "Vendor Created", Status = "200" });
|
||||
}
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<IActionResult> Update(int id, [FromBody] EditVendor_DTO model)
|
||||
[HttpPost("Update")]
|
||||
public async Task<IActionResult> Update([FromRoute] int? id, [FromBody] EditVendor_DTO model)
|
||||
{
|
||||
try
|
||||
{
|
||||
var exist = await _db.Vendors.FindAsync(id);
|
||||
if (exist == null)
|
||||
return BadRequest(new Response { Status = "Error", Message = "Vendor not found" });
|
||||
// For named route, id comes from model
|
||||
int vendorId = id ?? model.Id;
|
||||
if (vendorId == 0)
|
||||
return BadRequest(new Response { Status = "Error", Message = "Vendor Id is required" });
|
||||
|
||||
exist.CompanyName = model.CompanyName;
|
||||
exist.ContactName = model.ContactName;
|
||||
exist.Email = model.Email;
|
||||
exist.Phone = model.Phone;
|
||||
exist.Address = model.Address;
|
||||
exist.City = model.City;
|
||||
exist.State = model.State;
|
||||
exist.Zip = model.Zip;
|
||||
exist.TradeSpecialties = model.TradeSpecialties;
|
||||
exist.IsActive = model.IsActive;
|
||||
var updateDto = new UpdateVendorDTO
|
||||
{
|
||||
Name = model.CompanyName
|
||||
};
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
await _vendorService.UpdateVendorAsync(vendorId, updateDto, userId);
|
||||
return Ok(new DataResponse { Message = "Vendor Updated", Status = "200" });
|
||||
}
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Vendor not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(new Response { Status = "Error", Message = ex.Message });
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
[HttpPost("Delete")]
|
||||
public async Task<IActionResult> Delete([FromRoute] int? id, [FromQuery] int? queryId = null)
|
||||
{
|
||||
var exist = await _db.Vendors.FindAsync(id);
|
||||
if (exist == null)
|
||||
return BadRequest(new Response { Status = "Error", Message = "Vendor not found" });
|
||||
try
|
||||
{
|
||||
// Support both route parameter and query string
|
||||
int vendorId = id ?? queryId ?? 0;
|
||||
if (vendorId == 0)
|
||||
return BadRequest(new Response { Status = "Error", Message = "Vendor Id is required" });
|
||||
|
||||
_db.Vendors.Remove(exist);
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok(new DataResponse { Message = "Vendor Deleted", Status = "200" });
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
await _vendorService.DeleteVendorAsync(vendorId, userId);
|
||||
return Ok(new DataResponse { Message = "Vendor Deleted", Status = "200" });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Vendor not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("Dropdown")]
|
||||
|
|
|
|||
|
|
@ -232,10 +232,10 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
Location = dispatch.WorkOrder?.Locations == null ? null : new
|
||||
{
|
||||
dispatch.WorkOrder.Locations.Name,
|
||||
dispatch.WorkOrder.Locations.Address,
|
||||
dispatch.WorkOrder.Locations.Address1,
|
||||
dispatch.WorkOrder.Locations.City,
|
||||
dispatch.WorkOrder.Locations.State,
|
||||
dispatch.WorkOrder.Locations.ZipCode
|
||||
dispatch.WorkOrder.Locations.Zip
|
||||
},
|
||||
Checklist = checklist,
|
||||
Signoffs = signoffs,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,7 @@
|
|||
namespace Api.SeaHavenIndustries.DTOs
|
||||
using Data.SeaHavenIndustries;
|
||||
using SeaHaven.Services.DTOs;
|
||||
|
||||
namespace Api.SeaHavenIndustries.DTOs
|
||||
{
|
||||
public class Contact_DTO
|
||||
{
|
||||
|
|
@ -20,5 +23,100 @@
|
|||
public string? TwitterUrl { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
public int? AccountId { get; set; }
|
||||
|
||||
// ✅ Map API DTO to Service DTO
|
||||
public CreateContactDTO ToServiceCreateDTO()
|
||||
{
|
||||
return new CreateContactDTO
|
||||
{
|
||||
FirstName = this.FirstName,
|
||||
LastName = this.LastName,
|
||||
Email = this.Email ?? "",
|
||||
Phone = this.PhoneNumber,
|
||||
Title = this.Title,
|
||||
Type = this.ContactType,
|
||||
AccountId = this.AccountId
|
||||
};
|
||||
}
|
||||
|
||||
// Map from Entity to DTO (Model)
|
||||
public static Contact_DTO MapToModel(Contacts entity)
|
||||
{
|
||||
return new Contact_DTO
|
||||
{
|
||||
Title = entity.Title,
|
||||
Owner = entity.Owner,
|
||||
FirstName = entity.FirstName,
|
||||
MiddleName = entity.MiddleName,
|
||||
LastName = entity.LastName,
|
||||
ContactType = entity.ContactType,
|
||||
PhoneNumber = entity.PhoneNumber,
|
||||
Email = entity.Email,
|
||||
Address1 = entity.Address1,
|
||||
Address2 = entity.Address2,
|
||||
City = entity.City,
|
||||
State = entity.State,
|
||||
Zip = entity.Zip,
|
||||
FacebookUrl = entity.FacebookUrl,
|
||||
LinkedInUrl = entity.LinkedInUrl,
|
||||
TwitterUrl = entity.TwitterUrl,
|
||||
LocationId = entity.LocationId,
|
||||
AccountId = entity.AccountId
|
||||
};
|
||||
}
|
||||
|
||||
// Map from DTO (Model) to Entity
|
||||
public static Contacts MapToEntity(Contact_DTO dto, Contacts? existingEntity = null)
|
||||
{
|
||||
var entity = existingEntity ?? new Contacts();
|
||||
|
||||
entity.Title = dto.Title;
|
||||
entity.Owner = dto.Owner;
|
||||
entity.FirstName = dto.FirstName;
|
||||
entity.MiddleName = dto.MiddleName;
|
||||
entity.LastName = dto.LastName;
|
||||
entity.ContactType = dto.ContactType;
|
||||
entity.PhoneNumber = dto.PhoneNumber;
|
||||
entity.Email = dto.Email;
|
||||
entity.Address1 = dto.Address1;
|
||||
entity.Address2 = dto.Address2;
|
||||
entity.City = dto.City;
|
||||
entity.State = dto.State;
|
||||
entity.Zip = dto.Zip;
|
||||
entity.FacebookUrl = dto.FacebookUrl;
|
||||
entity.LinkedInUrl = dto.LinkedInUrl;
|
||||
entity.TwitterUrl = dto.TwitterUrl;
|
||||
entity.LocationId = dto.LocationId;
|
||||
entity.AccountId = dto.AccountId;
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
// DTO for contact list view
|
||||
public class ContactListItem_DTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
|
||||
// ✅ Map from Service DTO
|
||||
public static ContactListItem_DTO FromServiceDTO(ContactDTO serviceDto)
|
||||
{
|
||||
return new ContactListItem_DTO
|
||||
{
|
||||
Id = serviceDto.Id,
|
||||
Name = (serviceDto.FirstName + " " + serviceDto.LastName).Trim(),
|
||||
Email = serviceDto.Email,
|
||||
Phone = serviceDto.Phone
|
||||
};
|
||||
}
|
||||
|
||||
// ✅ Map collection from Service DTOs
|
||||
public static IEnumerable<ContactListItem_DTO> FromServiceDTOs(IEnumerable<ContactDTO> serviceDtos)
|
||||
{
|
||||
return serviceDtos.Select(FromServiceDTO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
namespace Api.SeaHavenIndustries.DTOs
|
||||
using Data.SeaHavenIndustries;
|
||||
using SeaHaven.Services.DTOs;
|
||||
|
||||
namespace Api.SeaHavenIndustries.DTOs
|
||||
{
|
||||
public class EditContact_DTO
|
||||
{
|
||||
|
|
@ -21,5 +24,72 @@
|
|||
public string? TwitterUrl { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
public int? AccountId { get; set; }
|
||||
|
||||
// ✅ Map API DTO to Service DTO
|
||||
public UpdateContactDTO ToServiceUpdateDTO()
|
||||
{
|
||||
return new UpdateContactDTO
|
||||
{
|
||||
FirstName = this.FirstName,
|
||||
LastName = this.LastName,
|
||||
Email = this.Email,
|
||||
Phone = this.PhoneNumber,
|
||||
Title = this.Title,
|
||||
Type = this.ContactType,
|
||||
AccountId = this.AccountId
|
||||
};
|
||||
}
|
||||
|
||||
// Map from Entity to DTO (Model)
|
||||
public static EditContact_DTO MapToModel(Contacts entity)
|
||||
{
|
||||
return new EditContact_DTO
|
||||
{
|
||||
Id = entity.Id,
|
||||
Title = entity.Title,
|
||||
Owner = entity.Owner,
|
||||
FirstName = entity.FirstName,
|
||||
MiddleName = entity.MiddleName,
|
||||
LastName = entity.LastName,
|
||||
ContactType = entity.ContactType,
|
||||
PhoneNumber = entity.PhoneNumber,
|
||||
Email = entity.Email,
|
||||
Address1 = entity.Address1,
|
||||
Address2 = entity.Address2,
|
||||
City = entity.City,
|
||||
State = entity.State,
|
||||
Zip = entity.Zip,
|
||||
FacebookUrl = entity.FacebookUrl,
|
||||
LinkedInUrl = entity.LinkedInUrl,
|
||||
TwitterUrl = entity.TwitterUrl,
|
||||
LocationId = entity.LocationId,
|
||||
AccountId = entity.AccountId
|
||||
};
|
||||
}
|
||||
|
||||
// Map from DTO (Model) to Entity - updates existing entity
|
||||
public static Contacts MapToEntity(EditContact_DTO dto, Contacts existingEntity)
|
||||
{
|
||||
existingEntity.Title = dto.Title;
|
||||
existingEntity.Owner = dto.Owner;
|
||||
existingEntity.FirstName = dto.FirstName;
|
||||
existingEntity.MiddleName = dto.MiddleName;
|
||||
existingEntity.LastName = dto.LastName;
|
||||
existingEntity.ContactType = dto.ContactType;
|
||||
existingEntity.PhoneNumber = dto.PhoneNumber;
|
||||
existingEntity.Email = dto.Email;
|
||||
existingEntity.Address1 = dto.Address1;
|
||||
existingEntity.Address2 = dto.Address2;
|
||||
existingEntity.City = dto.City;
|
||||
existingEntity.State = dto.State;
|
||||
existingEntity.Zip = dto.Zip;
|
||||
existingEntity.FacebookUrl = dto.FacebookUrl;
|
||||
existingEntity.LinkedInUrl = dto.LinkedInUrl;
|
||||
existingEntity.TwitterUrl = dto.TwitterUrl;
|
||||
existingEntity.LocationId = dto.LocationId;
|
||||
existingEntity.AccountId = dto.AccountId;
|
||||
|
||||
return existingEntity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
48
Api.SeaHavenIndustries/DTOs/EditEvent_DTO.cs
Normal file
48
Api.SeaHavenIndustries/DTOs/EditEvent_DTO.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Api.SeaHavenIndustries.DTOs
|
||||
{
|
||||
/// <summary>
|
||||
/// DTO for POST/PUT requests - Create/Edit Event
|
||||
/// Used to create or update event information
|
||||
/// </summary>
|
||||
public class EditEvent_DTO
|
||||
{
|
||||
public int? Id { get; set; } // Null for create, populated for edit
|
||||
|
||||
[Required(ErrorMessage = "Please enter a title")]
|
||||
[StringLength(100, ErrorMessage = "Title cannot be longer than 100 characters")]
|
||||
public string Title { get; set; } = null!;
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
[StringLength(150, ErrorMessage = "Location cannot be longer than 150 characters")]
|
||||
public string? Location { get; set; }
|
||||
|
||||
public int? RecordID { get; set; }
|
||||
public string? RecordName { get; set; }
|
||||
public string? RecordTargetType { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "The Remind Attendees field is required")]
|
||||
public int ReminderMinutes { get; set; } = 15;
|
||||
|
||||
[Required(ErrorMessage = "The Select a Calendar Color field is required")]
|
||||
public string EventColor { get; set; } = "primary";
|
||||
|
||||
[Required(ErrorMessage = "Please select a start date")]
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please select a start time")]
|
||||
public string StartTime { get; set; } = null!;
|
||||
|
||||
[Required(ErrorMessage = "Please select an end date")]
|
||||
public DateTime EndDate { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please select an end time")]
|
||||
public string EndTime { get; set; } = null!;
|
||||
|
||||
public bool AllDay { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using SeaHaven.Services.DTOs;
|
||||
|
||||
namespace Api.SeaHavenIndustries.DTOs
|
||||
{
|
||||
public class EditLocation_DTO
|
||||
|
|
@ -17,5 +19,19 @@ namespace Api.SeaHavenIndustries.DTOs
|
|||
|
||||
public int? GetAccountId() =>
|
||||
int.TryParse(AccountId, out var id) ? id : null;
|
||||
|
||||
// ✅ Map API DTO to Service DTO
|
||||
public UpdateLocationDTO ToServiceUpdateDTO()
|
||||
{
|
||||
return new UpdateLocationDTO
|
||||
{
|
||||
LocationName = this.Name,
|
||||
Address = this.Address,
|
||||
City = this.City,
|
||||
State = this.State,
|
||||
Zipcode = this.ZipCode,
|
||||
AccountId = this.GetAccountId()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
33
Api.SeaHavenIndustries/DTOs/Event_DTO.cs
Normal file
33
Api.SeaHavenIndustries/DTOs/Event_DTO.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
|
||||
namespace Api.SeaHavenIndustries.DTOs
|
||||
{
|
||||
/// <summary>
|
||||
/// DTO for GET responses - Event data
|
||||
/// Used to return event information to the client
|
||||
/// </summary>
|
||||
public class Event_DTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; } = null!;
|
||||
public string? Description { get; set; }
|
||||
public string? Location { get; set; }
|
||||
|
||||
public int? RecordID { get; set; }
|
||||
public string? RecordName { get; set; }
|
||||
public string? RecordTargetType { get; set; }
|
||||
|
||||
public int ReminderMinutes { get; set; }
|
||||
public string EventColor { get; set; } = null!;
|
||||
|
||||
public DateTime StartDate { get; set; }
|
||||
public string? StartTime { get; set; }
|
||||
public DateTime EndDate { get; set; }
|
||||
public string? EndTime { get; set; }
|
||||
public bool AllDay { get; set; }
|
||||
|
||||
public DateTime? CreatedDate { get; set; }
|
||||
public string? createdby { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using SeaHaven.Services.DTOs;
|
||||
|
||||
namespace Api.SeaHavenIndustries.DTOs
|
||||
{
|
||||
public class Location_DTO
|
||||
|
|
@ -16,5 +19,117 @@ namespace Api.SeaHavenIndustries.DTOs
|
|||
|
||||
public int? GetAccountId() =>
|
||||
int.TryParse(AccountId, out var id) ? id : null;
|
||||
|
||||
// ✅ Map API DTO to Service DTO
|
||||
public CreateLocationDTO ToServiceCreateDTO()
|
||||
{
|
||||
return new CreateLocationDTO
|
||||
{
|
||||
LocationName = this.Name ?? "",
|
||||
Address = this.Address,
|
||||
City = this.City,
|
||||
State = this.State,
|
||||
Zipcode = this.ZipCode,
|
||||
AccountId = this.GetAccountId()
|
||||
};
|
||||
}
|
||||
|
||||
// Map from Entity to DTO
|
||||
public static Location_DTO MapToModel(Locations entity)
|
||||
{
|
||||
return new Location_DTO
|
||||
{
|
||||
Title = entity.Title,
|
||||
Name = entity.Name,
|
||||
Address = entity.Address1,
|
||||
City = entity.City,
|
||||
State = entity.State,
|
||||
ZipCode = entity.Zip,
|
||||
Phone = entity.PhoneNumber,
|
||||
Contact = null, // Not in database schema
|
||||
ContactEmail = entity.Email,
|
||||
Status = entity.Status,
|
||||
AccountId = null // Not in database schema
|
||||
};
|
||||
}
|
||||
|
||||
// Map from DTO to Entity
|
||||
public static Locations MapToEntity(Location_DTO dto, Locations? existingEntity = null)
|
||||
{
|
||||
var entity = existingEntity ?? new Locations();
|
||||
|
||||
entity.Title = dto.Title;
|
||||
entity.Name = dto.Name;
|
||||
entity.Address1 = dto.Address;
|
||||
entity.City = dto.City;
|
||||
entity.State = dto.State;
|
||||
entity.Zip = dto.ZipCode;
|
||||
entity.PhoneNumber = dto.Phone;
|
||||
// Contact field doesn't exist in database schema - ignored
|
||||
entity.Email = dto.ContactEmail;
|
||||
entity.Status = dto.Status;
|
||||
// AccountId field doesn't exist in database schema - ignored
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
// DTO for list views
|
||||
public class LocationListItem_DTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Title { get; set; }
|
||||
|
||||
// Map from Entity
|
||||
public static LocationListItem_DTO MapToModel(Locations entity)
|
||||
{
|
||||
return new LocationListItem_DTO
|
||||
{
|
||||
Id = entity.Id,
|
||||
Name = entity.Name,
|
||||
Title = entity.Title
|
||||
};
|
||||
}
|
||||
|
||||
// ✅ Map from Service DTO (no logic in controller)
|
||||
public static LocationListItem_DTO FromServiceDTO(LocationDTO serviceDto)
|
||||
{
|
||||
return new LocationListItem_DTO
|
||||
{
|
||||
Id = serviceDto.Id,
|
||||
Name = serviceDto.Name ?? serviceDto.Title,
|
||||
Title = serviceDto.Title ?? serviceDto.Name
|
||||
};
|
||||
}
|
||||
|
||||
// ✅ Map collection from Service DTOs
|
||||
public static IEnumerable<LocationListItem_DTO> FromServiceDTOs(IEnumerable<LocationDTO> serviceDtos)
|
||||
{
|
||||
return serviceDtos.Select(FromServiceDTO);
|
||||
}
|
||||
}
|
||||
|
||||
// DTO for location dropdown/simple view
|
||||
public class LocationViewModel_DTO
|
||||
{
|
||||
public int? Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
// ✅ Map from Service DTO
|
||||
public static LocationViewModel_DTO FromServiceDTO(LocationDTO serviceDto)
|
||||
{
|
||||
return new LocationViewModel_DTO
|
||||
{
|
||||
Id = serviceDto.Id,
|
||||
Name = serviceDto.Name ?? serviceDto.Title
|
||||
};
|
||||
}
|
||||
|
||||
// ✅ Map collection from Service DTOs
|
||||
public static IEnumerable<LocationViewModel_DTO> FromServiceDTOs(IEnumerable<LocationDTO> serviceDtos)
|
||||
{
|
||||
return serviceDtos.Select(FromServiceDTO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Data.SeaHavenIndustries.Enums;
|
||||
|
||||
namespace Api.SeaHavenIndustries.DTOs
|
||||
{
|
||||
|
|
@ -9,14 +10,11 @@ namespace Api.SeaHavenIndustries.DTOs
|
|||
public string? Description { get; set; }
|
||||
public string? PO { get; set; }
|
||||
public string? TT { get; set; }
|
||||
//public int? ContactId { get; set; }
|
||||
public string? AssignTo { get; set; }
|
||||
public DateTime? DueDate { get; set; }
|
||||
public string? Priority { get; set; }
|
||||
public WorkOrderPriority? Priority { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
public bool? istemplate { get; set; }
|
||||
//public int? CategoryId { get; set; }
|
||||
|
||||
public List<IFormFile>? Attachments { get; set; }
|
||||
public string? BeforPhotoIssue { get; set; }
|
||||
public IFormFile? BeforPhotoAttachment { get; set; }
|
||||
|
|
@ -25,15 +23,33 @@ namespace Api.SeaHavenIndustries.DTOs
|
|||
public string? SignOffName { get; set; }
|
||||
public IFormFile? SignOffAttachment { get; set; }
|
||||
public string? SignOffSignature { get; set; }
|
||||
public string? WorkOrderContacts { get; set; }
|
||||
public string? WorkOrderCategories { get; set; }
|
||||
public List<int> GetContactIds()
|
||||
public List<int>? ContactIds { get; set; }
|
||||
public List<int>? CategoryIds { get; set; }
|
||||
|
||||
public static WorkOrder MapToEntity(Workorder_DTO dto, string userId)
|
||||
{
|
||||
return WorkOrderContacts?.Split(',').Select(int.Parse).ToList() ?? new List<int>();
|
||||
}
|
||||
public List<int> GetCategoryIds()
|
||||
{
|
||||
return WorkOrderCategories?.Split(',').Select(int.Parse).ToList() ?? new List<int>();
|
||||
if (dto == null) return null!;
|
||||
|
||||
return new WorkOrder
|
||||
{
|
||||
WorkerOrderNumber = dto.WorkerOrderNumber,
|
||||
WorkerOrderTitle = dto.WorkerOrderTitle,
|
||||
Description = dto.Description,
|
||||
PO = dto.PO,
|
||||
TT = dto.TT,
|
||||
AssignTo = dto.AssignTo,
|
||||
DueDate = dto.DueDate,
|
||||
Priority = (dto.Priority ?? WorkOrderPriority.Medium).ToString(),
|
||||
LocationId = dto.LocationId,
|
||||
istemplate = dto.istemplate ?? false,
|
||||
Status = WorkOrderStatus.Open.ToString(),
|
||||
BeforPhotoIssue = dto.BeforPhotoIssue,
|
||||
AfterPhotoIssue = dto.AfterPhotoIssue,
|
||||
SignOffName = dto.SignOffName,
|
||||
SignOffSignature = dto.SignOffSignature,
|
||||
createdby = userId,
|
||||
CreatedDate = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
public class EditWorkorder_DTO
|
||||
|
|
@ -46,8 +62,8 @@ namespace Api.SeaHavenIndustries.DTOs
|
|||
public string? TT { get; set; }
|
||||
public string? AssignTo { get; set; }
|
||||
public DateTime? DueDate { get; set; }
|
||||
public string? Priority { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public WorkOrderPriority? Priority { get; set; }
|
||||
public WorkOrderStatus? Status { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
public string? Problem { get; set; }
|
||||
public string? Trade { get; set; }
|
||||
|
|
@ -56,7 +72,6 @@ namespace Api.SeaHavenIndustries.DTOs
|
|||
public DateTime? ScheduledDate { get; set; }
|
||||
public DateTime? CompletedDate { get; set; }
|
||||
public string? Source { get; set; }
|
||||
|
||||
public List<IFormFile>? Attachments { get; set; }
|
||||
public string? BeforPhotoIssue { get; set; }
|
||||
public IFormFile? BeforPhotoAttachment { get; set; }
|
||||
|
|
@ -65,16 +80,36 @@ namespace Api.SeaHavenIndustries.DTOs
|
|||
public string? SignOffName { get; set; }
|
||||
public IFormFile? SignOffAttachment { get; set; }
|
||||
public string? SignOffSignature { get; set; }
|
||||
public string? WorkOrderContacts { get; set; }
|
||||
public string? WorkOrderCategories { get; set; }
|
||||
public List<int> GetContactIds()
|
||||
public List<int>? ContactIds { get; set; }
|
||||
public List<int>? CategoryIds { get; set; }
|
||||
|
||||
public static void UpdateEntity(EditWorkorder_DTO dto, WorkOrder entity)
|
||||
{
|
||||
return WorkOrderContacts?.Split(',').Select(int.Parse).ToList() ?? new List<int>();
|
||||
}
|
||||
public List<int> GetCategoryIds()
|
||||
{
|
||||
return WorkOrderCategories?.Split(',').Select(int.Parse).ToList() ?? new List<int>();
|
||||
if (dto == null || entity == null) return;
|
||||
|
||||
if (dto.WorkerOrderNumber != null) entity.WorkerOrderNumber = dto.WorkerOrderNumber;
|
||||
if (dto.WorkerOrderTitle != null) entity.WorkerOrderTitle = dto.WorkerOrderTitle;
|
||||
if (dto.Description != null) entity.Description = dto.Description;
|
||||
if (dto.PO != null) entity.PO = dto.PO;
|
||||
if (dto.TT != null) entity.TT = dto.TT;
|
||||
if (dto.AssignTo != null) entity.AssignTo = dto.AssignTo;
|
||||
if (dto.DueDate.HasValue) entity.DueDate = dto.DueDate;
|
||||
if (dto.Priority.HasValue) entity.Priority = dto.Priority.ToString()!;
|
||||
if (dto.Status.HasValue) entity.Status = dto.Status.ToString()!;
|
||||
if (dto.LocationId.HasValue) entity.LocationId = dto.LocationId;
|
||||
if (dto.Problem != null) entity.Problem = dto.Problem;
|
||||
if (dto.Trade != null) entity.Trade = dto.Trade;
|
||||
if (dto.SubTrade != null) entity.SubTrade = dto.SubTrade;
|
||||
if (dto.VendorNTE.HasValue) entity.VendorNTE = dto.VendorNTE;
|
||||
if (dto.ScheduledDate.HasValue) entity.ScheduledDate = dto.ScheduledDate;
|
||||
if (dto.CompletedDate.HasValue) entity.CompletedDate = dto.CompletedDate;
|
||||
if (dto.Source != null) entity.Source = dto.Source;
|
||||
if (dto.BeforPhotoIssue != null) entity.BeforPhotoIssue = dto.BeforPhotoIssue;
|
||||
if (dto.AfterPhotoIssue != null) entity.AfterPhotoIssue = dto.AfterPhotoIssue;
|
||||
if (dto.SignOffName != null) entity.SignOffName = dto.SignOffName;
|
||||
if (dto.SignOffSignature != null) entity.SignOffSignature = dto.SignOffSignature;
|
||||
|
||||
entity.LastModificationTime = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -27,19 +27,19 @@ namespace Api.SeaHavenIndustries.Helper
|
|||
//mail.Body = body;
|
||||
//mail.IsBodyHtml = true;
|
||||
//SmtpClient smtp = new SmtpClient("mail.codevoir.com");
|
||||
//NetworkCredential Credentials = new NetworkCredential("infoesquall@codevoir.com", "Abc123!@#");
|
||||
//NetworkCredential Credentials = new NetworkCredential("<smtp-user>", "<smtp-password>");
|
||||
//smtp.UseDefaultCredentials = false;
|
||||
//smtp.Credentials = Credentials;
|
||||
//smtp.Port = 8889; //25 alternative port number is 8889
|
||||
//smtp.EnableSsl = false;
|
||||
|
||||
//smtp.Send(mail);
|
||||
//var apiKey = Environment.GetEnvironmentVariable("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\r\n");
|
||||
//var apiKey1 = Environment.GetEnvironmentVariable("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");
|
||||
//var apiKey = Environment.GetEnvironmentVariable("<SENDGRID_API_KEY>\r\n");
|
||||
//var apiKey1 = Environment.GetEnvironmentVariable("<SENDGRID_API_KEY>");
|
||||
// var apiKey2 = Environment.GetEnvironmentVariable("SENDGRID_API_KEY");
|
||||
var apiKey = _configuration.GetValue<string>("SendGrid:ApiKey");
|
||||
|
||||
//var apiKey = "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";
|
||||
//var apiKey = "<SENDGRID_API_KEY>";
|
||||
var client = new SendGridClient(apiKey);
|
||||
var from = new EmailAddress("tech@seahavenind.com", "Sea haven Industries");
|
||||
//var subject = "Sending with SendGrid is Fun";
|
||||
|
|
@ -65,7 +65,7 @@ namespace Api.SeaHavenIndustries.Helper
|
|||
|
||||
var apiKey = _configuration.GetValue<string>("SendGrid:ApiKey");
|
||||
|
||||
//var apiKey = "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";
|
||||
//var apiKey = "<SENDGRID_API_KEY>";
|
||||
var client = new SendGridClient(apiKey);
|
||||
var from = new EmailAddress("tech@seahavenind.com", "Sea haven Industries");
|
||||
//var subject = "Sending with SendGrid is Fun";
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ using Microsoft.EntityFrameworkCore;
|
|||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using System.Text;
|
||||
using SeaHaven.DataServices.DependencyInjection;
|
||||
using SeaHaven.Services.DependencyInjection;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
ConfigurationManager configuration = builder.Configuration;
|
||||
|
|
@ -31,6 +33,7 @@ builder.Services.AddResponseCompression(opts =>
|
|||
});
|
||||
builder.Services.AddCors(option =>
|
||||
option.AddDefaultPolicy(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()));
|
||||
|
||||
builder.Services.AddScoped<SendMessage>();
|
||||
builder.Services.AddScoped<Api.SeaHavenIndustries.Helper.VendorPortalTokenService>();
|
||||
builder.Services.AddSingleton<Api.SeaHavenIndustries.Helper.ZipCodeDistance>(sp =>
|
||||
|
|
@ -41,6 +44,10 @@ builder.Services.AddSingleton<Api.SeaHavenIndustries.Helper.ZipCodeDistance>(sp
|
|||
if (File.Exists(csvPath)) zipService.Load(csvPath);
|
||||
return zipService;
|
||||
});
|
||||
|
||||
builder.Services.AddDataServices();
|
||||
builder.Services.AddBusinessServices();
|
||||
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
|
|
@ -111,60 +118,60 @@ app.UseAuthorization();
|
|||
|
||||
app.MapControllers();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
using var scope = app.Services.CreateScope();
|
||||
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
|
||||
//if (app.Environment.IsDevelopment())
|
||||
//{
|
||||
// using var scope = app.Services.CreateScope();
|
||||
// var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||
// var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
|
||||
|
||||
if (!await roleManager.RoleExistsAsync("Admin"))
|
||||
await roleManager.CreateAsync(new IdentityRole("Admin"));
|
||||
if (!await roleManager.RoleExistsAsync("Dispatcher"))
|
||||
await roleManager.CreateAsync(new IdentityRole("Dispatcher"));
|
||||
if (!await roleManager.RoleExistsAsync("Manager"))
|
||||
await roleManager.CreateAsync(new IdentityRole("Manager"));
|
||||
// if (!await roleManager.RoleExistsAsync("Admin"))
|
||||
// await roleManager.CreateAsync(new IdentityRole("Admin"));
|
||||
// if (!await roleManager.RoleExistsAsync("Dispatcher"))
|
||||
// await roleManager.CreateAsync(new IdentityRole("Dispatcher"));
|
||||
// if (!await roleManager.RoleExistsAsync("Manager"))
|
||||
// await roleManager.CreateAsync(new IdentityRole("Manager"));
|
||||
|
||||
var adminEmail = "admin@seahavenind.com";
|
||||
if (await userManager.FindByNameAsync(adminEmail) == null)
|
||||
{
|
||||
var admin = new ApplicationUser
|
||||
{
|
||||
UserName = adminEmail,
|
||||
Email = adminEmail,
|
||||
FirstName = "Admin",
|
||||
LastName = "User",
|
||||
EmailConfirmed = true,
|
||||
UniqueName = "Active",
|
||||
CreatedDate = DateTime.Now
|
||||
};
|
||||
var result = await userManager.CreateAsync(admin, "Admin@123");
|
||||
if (result.Succeeded)
|
||||
await userManager.AddToRoleAsync(admin, "Admin");
|
||||
}
|
||||
// var adminEmail = "admin@seahavenind.com";
|
||||
// if (await userManager.FindByNameAsync(adminEmail) == null)
|
||||
// {
|
||||
// var admin = new ApplicationUser
|
||||
// {
|
||||
// UserName = adminEmail,
|
||||
// Email = adminEmail,
|
||||
// FirstName = "Admin",
|
||||
// LastName = "User",
|
||||
// EmailConfirmed = true,
|
||||
// UniqueName = "Active",
|
||||
// CreatedDate = DateTime.Now
|
||||
// };
|
||||
// var result = await userManager.CreateAsync(admin, "Admin@123");
|
||||
// if (result.Succeeded)
|
||||
// await userManager.AddToRoleAsync(admin, "Admin");
|
||||
// }
|
||||
|
||||
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
if (!db.DropdownOptions.Any())
|
||||
{
|
||||
var trades = new[] { "Plumbing", "Electrical", "HVAC", "Fire/Life Safety", "General", "Roofing", "Janitorial", "Painting", "Locksmith", "Carpentry" };
|
||||
for (int i = 0; i < trades.Length; i++)
|
||||
db.DropdownOptions.Add(new DropdownOption { Category = "Trade", Value = trades[i], SortOrder = i });
|
||||
// var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
// if (!db.DropdownOptions.Any())
|
||||
// {
|
||||
// var trades = new[] { "Plumbing", "Electrical", "HVAC", "Fire/Life Safety", "General", "Roofing", "Janitorial", "Painting", "Locksmith", "Carpentry" };
|
||||
// for (int i = 0; i < trades.Length; i++)
|
||||
// db.DropdownOptions.Add(new DropdownOption { Category = "Trade", Value = trades[i], SortOrder = i });
|
||||
|
||||
var subTrades = new (string trade, string sub)[] {
|
||||
("Plumbing", "Drain Cleaning"), ("Plumbing", "Pipe Repair"), ("Plumbing", "Fixture Install"), ("Plumbing", "Backflow Testing"),
|
||||
("Electrical", "Lighting"), ("Electrical", "Panel/Breaker"), ("Electrical", "Wiring"), ("Electrical", "Generator"),
|
||||
("HVAC", "AC Repair"), ("HVAC", "Heating"), ("HVAC", "Ductwork"), ("HVAC", "Controls/BMS"),
|
||||
("Fire/Life Safety", "Sprinkler"), ("Fire/Life Safety", "Fire Alarm"), ("Fire/Life Safety", "Extinguisher"), ("Fire/Life Safety", "Fire Pump"),
|
||||
("General", "Doors"), ("General", "Flooring"), ("General", "Drywall"), ("General", "Concrete"),
|
||||
};
|
||||
for (int i = 0; i < subTrades.Length; i++)
|
||||
db.DropdownOptions.Add(new DropdownOption { Category = "SubTrade", Value = subTrades[i].sub, ParentValue = subTrades[i].trade, SortOrder = i });
|
||||
// var subTrades = new (string trade, string sub)[] {
|
||||
// ("Plumbing", "Drain Cleaning"), ("Plumbing", "Pipe Repair"), ("Plumbing", "Fixture Install"), ("Plumbing", "Backflow Testing"),
|
||||
// ("Electrical", "Lighting"), ("Electrical", "Panel/Breaker"), ("Electrical", "Wiring"), ("Electrical", "Generator"),
|
||||
// ("HVAC", "AC Repair"), ("HVAC", "Heating"), ("HVAC", "Ductwork"), ("HVAC", "Controls/BMS"),
|
||||
// ("Fire/Life Safety", "Sprinkler"), ("Fire/Life Safety", "Fire Alarm"), ("Fire/Life Safety", "Extinguisher"), ("Fire/Life Safety", "Fire Pump"),
|
||||
// ("General", "Doors"), ("General", "Flooring"), ("General", "Drywall"), ("General", "Concrete"),
|
||||
// };
|
||||
// for (int i = 0; i < subTrades.Length; i++)
|
||||
// db.DropdownOptions.Add(new DropdownOption { Category = "SubTrade", Value = subTrades[i].sub, ParentValue = subTrades[i].trade, SortOrder = i });
|
||||
|
||||
var problems = new[] { "Leak", "No Power", "Not Cooling", "Not Heating", "Clogged", "Broken/Damaged", "Alarm Fault", "Preventive Maintenance", "Inspection", "Installation", "Other" };
|
||||
for (int i = 0; i < problems.Length; i++)
|
||||
db.DropdownOptions.Add(new DropdownOption { Category = "Problem", Value = problems[i], SortOrder = i });
|
||||
// var problems = new[] { "Leak", "No Power", "Not Cooling", "Not Heating", "Clogged", "Broken/Damaged", "Alarm Fault", "Preventive Maintenance", "Inspection", "Installation", "Other" };
|
||||
// for (int i = 0; i < problems.Length; i++)
|
||||
// db.DropdownOptions.Add(new DropdownOption { Category = "Problem", Value = problems[i], SortOrder = i });
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
// await db.SaveChangesAsync();
|
||||
// }
|
||||
//}
|
||||
|
||||
app.Run();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=localhost,1433;Database=SeahavenIndustries;User Id=sa;Password=Local@Dev2026!;TrustServerCertificate=True;MultipleActiveResultSets=true"
|
||||
//"DefaultConnection": "Server=DESKTOP-64LC14O\SQLEXPRESS;Database=SeahavenIndustries;TrustServerCertificate=True;Trusted_Connection=True;MultipleActiveResultSets=true",
|
||||
|
||||
|
||||
// Provide the real value via environment variable ConnectionStrings__DefaultConnection or user-secrets.
|
||||
// Do NOT commit credentials. This file is committed, so keep only the placeholder here.
|
||||
"DefaultConnection": "${CONNECTION_STRING}"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
|
|
@ -9,20 +14,14 @@
|
|||
}
|
||||
},
|
||||
"SendGrid": {
|
||||
"ApiKey": "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"
|
||||
// Provide the real value via environment variable SendGrid__ApiKey or user-secrets.
|
||||
"ApiKey": "${SENDGRID_API_KEY}"
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"JWT": {
|
||||
"ValidAudience": "http://localhost:3000",
|
||||
"ValidIssuer": "https://localhost:7195",
|
||||
"Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr"
|
||||
},
|
||||
"FrontendBaseUrl": "http://localhost:3000",
|
||||
"VendorPortal": {
|
||||
"TokenLifetimeDays": 365
|
||||
},
|
||||
"Approvals": {
|
||||
"UpliftTier1MaxUsd": 2500,
|
||||
"Tier1Roles": [ "Admin", "Dispatcher" ],
|
||||
"Tier2Roles": [ "Admin", "Manager" ]
|
||||
"ValidAudience": "http://localhost:4200",
|
||||
"ValidIssuer": "http://localhost:7195",
|
||||
// Provide the real value via environment variable JWT__Secret or user-secrets.
|
||||
"Secret": "${JWT_SECRET}"
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Api.SeaHavenIndustries/appsettings.Production.json
Normal file
24
Api.SeaHavenIndustries/appsettings.Production.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"ConnectionStrings": {
|
||||
// Provide the real value via environment variable ConnectionStrings__DefaultConnection
|
||||
// (e.g. Elastic Beanstalk environment property) or the instance secret store. Do NOT commit credentials.
|
||||
"DefaultConnection": "${CONNECTION_STRING}"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"SendGrid": {
|
||||
// Provide the real value via environment variable SendGrid__ApiKey or the instance secret store.
|
||||
"ApiKey": "${SENDGRID_API_KEY}"
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"JWT": {
|
||||
"ValidAudience": "http://console.seahavenind.com",
|
||||
"ValidIssuer": "http://console.seahavenind.com",
|
||||
// Provide the real value via environment variable JWT__Secret or the instance secret store.
|
||||
"Secret": "${JWT_SECRET}"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": ""
|
||||
//"DefaultConnection": "Server=DESKTOP-64LC14O\SQLEXPRESS;Database=SeahavenIndustries;TrustServerCertificate=True;Trusted_Connection=True;MultipleActiveResultSets=true",
|
||||
|
||||
|
||||
// Provide the real value via environment variable ConnectionStrings__DefaultConnection,
|
||||
// user-secrets, or appsettings.Development.json (which is gitignored). Do NOT commit credentials.
|
||||
"DefaultConnection": "${CONNECTION_STRING}"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
|
|
@ -9,17 +14,14 @@
|
|||
}
|
||||
},
|
||||
"SendGrid": {
|
||||
"ApiKey": ""
|
||||
// Provide the real value via environment variable SendGrid__ApiKey or user-secrets.
|
||||
"ApiKey": "${SENDGRID_API_KEY}"
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"JWT": {
|
||||
"ValidAudience": "http://localhost:4200",
|
||||
"ValidIssuer": "http://localhost:7195",
|
||||
"Secret": ""
|
||||
},
|
||||
"Approvals": {
|
||||
"UpliftTier1MaxUsd": 2500,
|
||||
"Tier1Roles": [ "Admin", "Dispatcher" ],
|
||||
"Tier2Roles": [ "Admin", "Manager" ]
|
||||
// Provide the real value via environment variable JWT__Secret or user-secrets.
|
||||
"Secret": "${JWT_SECRET}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
|
||||
{
|
||||
"region" : "us-east-1",
|
||||
"configuration" : "Debug",
|
||||
"framework" : "net8.0",
|
||||
"self-contained" : true,
|
||||
"application" : "SeaHavenIndustries",
|
||||
"environment" : "SeaHavenIndustries-dev",
|
||||
"enable-xray" : false,
|
||||
"enhanced-health-type" : "basic",
|
||||
"additional-options" : "",
|
||||
"proxy-server" : "nginx"
|
||||
"region": "us-east-1",
|
||||
"configuration": "Debug",
|
||||
"framework": "net8.0",
|
||||
"self-contained": true,
|
||||
"application": "SeaHavenIndustries",
|
||||
"environment": "SeaHavenIndustries-dev",
|
||||
"enable-xray": false,
|
||||
"enhanced-health-type": "basic",
|
||||
"additional-options": "",
|
||||
"proxy-server": "nginx"
|
||||
}
|
||||
|
|
@ -924,6 +924,206 @@ PRIMARY KEY CLUSTERED
|
|||
GO
|
||||
|
||||
|
||||
|
||||
-- ==================================================================
|
||||
-- DATABASE CHANGES APPLIED ON 2026-04-22
|
||||
-- Migration: Calendar Module + Missing Column Fixes
|
||||
-- Applied by: AI Agent
|
||||
-- ==================================================================
|
||||
|
||||
-- ==================================================================
|
||||
-- 1. EVENTS TABLE (Calendar Module)
|
||||
-- Migration: 20260422021353_AddEventsTable
|
||||
-- ==================================================================
|
||||
|
||||
USE [db_a7141e_seahavenindustry]
|
||||
GO
|
||||
|
||||
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Events]') AND type in (N'U'))
|
||||
BEGIN
|
||||
CREATE TABLE [dbo].[Events](
|
||||
[Id] [int] IDENTITY(1,1) NOT NULL,
|
||||
|
||||
-- Basic Event Information
|
||||
[Title] [nvarchar](100) NOT NULL,
|
||||
[Description] [nvarchar](max) NULL,
|
||||
[Location] [nvarchar](150) NULL,
|
||||
|
||||
-- Related Record (Link to Accounts, Contacts, Work Orders, etc.)
|
||||
[RecordID] [int] NULL,
|
||||
[RecordName] [nvarchar](max) NULL,
|
||||
[RecordTargetType] [nvarchar](max) NULL,
|
||||
|
||||
-- Reminder & Display Settings
|
||||
[ReminderMinutes] [int] NOT NULL CONSTRAINT [DF_Events_ReminderMinutes] DEFAULT ((15)),
|
||||
[EventColor] [nvarchar](20) NOT NULL CONSTRAINT [DF_Events_EventColor] DEFAULT (N'primary'),
|
||||
|
||||
-- Schedule Information
|
||||
[StartDate] [datetime2](7) NOT NULL,
|
||||
[StartTime] [nvarchar](10) NULL,
|
||||
[EndDate] [datetime2](7) NOT NULL,
|
||||
[EndTime] [nvarchar](10) NULL,
|
||||
[AllDay] [bit] NOT NULL CONSTRAINT [DF_Events_AllDay] DEFAULT ((0)),
|
||||
|
||||
-- Audit Fields (from FullAuditEntity)
|
||||
[IsDeleted] [bit] NOT NULL CONSTRAINT [DF_Events_IsDeleted] DEFAULT ((0)),
|
||||
[DeletionTime] [datetime2](7) NULL,
|
||||
[createdby] [nvarchar](max) NULL,
|
||||
[CreatedDate] [datetime2](7) NULL,
|
||||
[lastmodifiedby] [nvarchar](max) NULL,
|
||||
[LastModificationTime] [datetime2](7) NULL,
|
||||
|
||||
CONSTRAINT [PK_Events] PRIMARY KEY CLUSTERED ([Id] ASC)
|
||||
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
|
||||
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
|
||||
END
|
||||
GO
|
||||
|
||||
-- Create Index on StartDate for faster calendar queries
|
||||
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_Events_StartDate' AND object_id = OBJECT_ID('[dbo].[Events]'))
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX [IX_Events_StartDate] ON [dbo].[Events]
|
||||
(
|
||||
[StartDate] ASC
|
||||
)
|
||||
WHERE ([IsDeleted] = 0)
|
||||
END
|
||||
GO
|
||||
|
||||
-- Create Index on IsDeleted for soft delete queries
|
||||
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_Events_IsDeleted' AND object_id = OBJECT_ID('[dbo].[Events]'))
|
||||
BEGIN
|
||||
CREATE NONCLUSTERED INDEX [IX_Events_IsDeleted] ON [dbo].[Events]
|
||||
(
|
||||
[IsDeleted] ASC
|
||||
)
|
||||
END
|
||||
GO
|
||||
|
||||
-- ==================================================================
|
||||
-- 2. WORKORDERS TABLE - MISSING COLUMNS
|
||||
-- Migrations: 20260416212330, 20260416224015, 20260417142453
|
||||
-- ==================================================================
|
||||
|
||||
-- Migration: 20260416224015_AddInternalWONumber
|
||||
ALTER TABLE [dbo].[WorkOrders]
|
||||
ADD [InternalWONumber] [nvarchar](max) NULL
|
||||
GO
|
||||
|
||||
-- Migration: 20260416212330_AddExternalSyncFields
|
||||
ALTER TABLE [dbo].[WorkOrders]
|
||||
ADD [ExternalWorkOrderId] [nvarchar](max) NULL,
|
||||
[Source] [nvarchar](max) NULL
|
||||
GO
|
||||
|
||||
-- Migration: 20260417142453_AddDropdownOptionsAndWOFields
|
||||
ALTER TABLE [dbo].[WorkOrders]
|
||||
ADD [Problem] [nvarchar](max) NULL,
|
||||
[Trade] [nvarchar](max) NULL,
|
||||
[SubTrade] [nvarchar](max) NULL,
|
||||
[VendorNTE] [decimal](18, 2) NULL,
|
||||
[ScheduledDate] [datetime2](7) NULL,
|
||||
[CompletedDate] [datetime2](7) NULL
|
||||
GO
|
||||
|
||||
-- ==================================================================
|
||||
-- 3. COMMENTS TABLE - MISSING COLUMNS
|
||||
-- Migrations: 20260416220656, 20260417171152
|
||||
-- ==================================================================
|
||||
|
||||
-- Migration: 20260416220656_AddCommentTypeAndAuditLog
|
||||
ALTER TABLE [dbo].[Comments]
|
||||
ADD [CommentType] [nvarchar](max) NULL,
|
||||
[Commenter] [nvarchar](max) NULL,
|
||||
[RecordType] [nvarchar](max) NULL
|
||||
GO
|
||||
|
||||
-- Migration: 20260417171152_AddDispatchNumberAndCommentDispatchId
|
||||
ALTER TABLE [dbo].[Comments]
|
||||
ADD [DispatchId] [int] NULL
|
||||
GO
|
||||
|
||||
-- ==================================================================
|
||||
-- 4. LOCATIONS TABLE - MISSING COLUMNS
|
||||
-- Migration: 20260416210833_AddLocationFields
|
||||
-- ==================================================================
|
||||
|
||||
ALTER TABLE [dbo].[Locations]
|
||||
ADD [Address1] [nvarchar](max) NULL,
|
||||
[Address2] [nvarchar](max) NULL,
|
||||
[City] [nvarchar](max) NULL,
|
||||
[State] [nvarchar](max) NULL,
|
||||
[Zip] [nvarchar](max) NULL,
|
||||
[PhoneNumber] [nvarchar](max) NULL,
|
||||
[Email] [nvarchar](max) NULL,
|
||||
[Status] [nvarchar](max) NULL
|
||||
GO
|
||||
|
||||
-- ==================================================================
|
||||
-- 5. MIGRATION HISTORY RECORDS
|
||||
-- Marked these migrations as applied to sync with code
|
||||
-- ==================================================================
|
||||
|
||||
INSERT INTO [dbo].[__EFMigrationsHistory] ([MigrationId], [ProductVersion])
|
||||
VALUES
|
||||
(N'20260416204109_SyncContactsAndModels', N'8.0.8'),
|
||||
(N'20260416210833_AddLocationFields', N'8.0.8'),
|
||||
(N'20260416212330_AddExternalSyncFields', N'8.0.8'),
|
||||
(N'20260416220656_AddCommentTypeAndAuditLog', N'8.0.8'),
|
||||
(N'20260416224015_AddInternalWONumber', N'8.0.8'),
|
||||
(N'20260417142453_AddDropdownOptionsAndWOFields', N'8.0.8'),
|
||||
(N'20260417153405_AddVendorAndDispatch', N'8.0.8'),
|
||||
(N'20260417154641_AddDispatchScheduledDate', N'8.0.8'),
|
||||
(N'20260417171152_AddDispatchNumberAndCommentDispatchId', N'8.0.8'),
|
||||
(N'20260417182910_AddTaskListTemplates', N'8.0.8'),
|
||||
(N'20260417190743_MultiWODispatch', N'8.0.8'),
|
||||
(N'20260417192012_AddDispatchChecklist', N'8.0.8'),
|
||||
(N'20260417194438_AddDispatchSignoffs', N'8.0.8'),
|
||||
(N'20260417195316_AddDispatchVerification', N'8.0.8'),
|
||||
(N'20260420151445_AddVendorAccessToken', N'8.0.8'),
|
||||
(N'20260420164708_AddDispatchUpliftRequest', N'8.0.8'),
|
||||
(N'20260422021353_AddEventsTable', N'8.0.8')
|
||||
GO
|
||||
|
||||
-- ==================================================================
|
||||
-- SUMMARY OF CHANGES (2026-04-22)
|
||||
-- ==================================================================
|
||||
-- Tables Created (11):
|
||||
-- 1. Events (Calendar/Events module)
|
||||
-- 2. WorkOrderAuditLogs (Audit trail for work order changes)
|
||||
-- 3. DropdownOptions (Dynamic dropdown values)
|
||||
-- 4. Vendors (Vendor management)
|
||||
-- 5. Dispatches (Vendor dispatch workflow)
|
||||
-- 6. TaskListTemplates (Reusable task templates)
|
||||
-- 7. TaskListTemplateItems (Template task items)
|
||||
-- 8. DispatchWorkOrders (Many-to-many: Dispatch <-> WorkOrders)
|
||||
-- 9. DispatchChecklistItems (Dispatch checklist)
|
||||
-- 10. DispatchSignoffs (Customer & Vendor signatures)
|
||||
-- 11. DispatchUpliftRequests (NTE increase requests)
|
||||
--
|
||||
-- Columns Added to WorkOrders (9):
|
||||
-- - InternalWONumber, ExternalWorkOrderId, Source
|
||||
-- - Problem, Trade, SubTrade
|
||||
-- - VendorNTE, ScheduledDate, CompletedDate
|
||||
--
|
||||
-- Columns Added to Comments (4):
|
||||
-- - CommentType, Commenter, RecordType, DispatchId
|
||||
--
|
||||
-- Columns Added to Locations (8):
|
||||
-- - Address1, Address2, City, State, Zip
|
||||
-- - PhoneNumber, Email, Status
|
||||
--
|
||||
-- Columns Added to Dispatches (7):
|
||||
-- - ScheduledDate, DispatchNumber, CompletedDate
|
||||
-- - VerifiedBy, VerifiedAt, AcceptToken, AcknowledgedAt
|
||||
--
|
||||
-- Columns Added to Vendors (1):
|
||||
-- - AccessToken
|
||||
--
|
||||
-- Migrations Applied: 17
|
||||
-- From: 20260416204109_SyncContactsAndModels
|
||||
-- To: 20260422021353_AddEventsTable
|
||||
--
|
||||
-- Database Status: UP TO DATE ✓
|
||||
-- ==================================================================
|
||||
|
||||
|
||||
|
|
|
|||
564
BACKEND_ARCHITECTURE.md
Normal file
564
BACKEND_ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,564 @@
|
|||
# Backend Architecture Guide
|
||||
|
||||
## 🏗️ Clean Architecture Overview
|
||||
|
||||
The backend follows **Clean Architecture** with strict layer separation:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ API LAYER (Controllers) │
|
||||
│ - HTTP Request/Response │
|
||||
│ - Authentication & Authorization │
|
||||
│ - Exception Handling │
|
||||
│ - Input Validation (basic) │
|
||||
└─────────────────┬───────────────────────────────────────────┘
|
||||
│ Calls
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ SERVICE LAYER (Business Logic) │
|
||||
│ - Business Rules │
|
||||
│ - FluentValidation (detailed validation) │
|
||||
│ - Data Transformation │
|
||||
│ - Orchestration │
|
||||
└─────────────────┬───────────────────────────────────────────┘
|
||||
│ Calls
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ DATA SERVICE LAYER (Persistence) │
|
||||
│ - Database Access │
|
||||
│ - Entity Operations (CRUD) │
|
||||
│ - Query Building │
|
||||
│ - Transaction Management │
|
||||
└─────────────────┬───────────────────────────────────────────┘
|
||||
│ Uses
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ DATA LAYER (EF Core) │
|
||||
│ - DbContext │
|
||||
│ - Entity Models │
|
||||
│ - Database Schema │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
backend/shoc-backend/
|
||||
│
|
||||
├── Api.SeaHavenIndustries/ # 🌐 API Layer
|
||||
│ ├── Controllers/ # HTTP endpoints
|
||||
│ ├── DTOs/ # Data Transfer Objects (API contracts)
|
||||
│ ├── Program.cs # App startup & DI registration
|
||||
│ └── appsettings.json # Configuration
|
||||
│
|
||||
├── SeaHaven.Services/ # 💼 Service Layer (Business Logic)
|
||||
│ ├── Interfaces/ # Service contracts (IContactService, etc.)
|
||||
│ ├── Implementation/ # Service implementations
|
||||
│ ├── DTOs/ # Internal DTOs for service layer
|
||||
│ └── Validators/ # FluentValidation validators
|
||||
│
|
||||
├── SeaHaven.DataServices/ # 💾 Data Access Layer
|
||||
│ ├── Interfaces/ # DataService contracts (IContactDataService, etc.)
|
||||
│ ├── Implementation/ # DataService implementations
|
||||
│ └── DTOs/ # Data layer DTOs
|
||||
│
|
||||
└── Data.SeaHavenIndustries/ # 🗄️ Data Layer (EF Core)
|
||||
├── Models/ # Entity models (Contact, Employee, etc.)
|
||||
├── ApplicationDbContext.cs # EF Core DbContext
|
||||
└── Migrations/ # Database migrations
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Data Flow Example (Creating a Contact)
|
||||
|
||||
### 1️⃣ **API Layer** (`ContactController.cs`)
|
||||
```csharp
|
||||
[HttpPost("Create")]
|
||||
public async Task<IActionResult> AddContacts(Contact_DTO dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get authenticated user ID
|
||||
var userId = _userManager.GetUserId(User);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
// Convert API DTO → Service DTO
|
||||
var serviceDto = dto.ToServiceCreateDTO();
|
||||
|
||||
// Call Service Layer
|
||||
await _contactService.CreateContactAsync(serviceDto, userId);
|
||||
|
||||
return Ok(new Response { Status = "Success", Message = "Contact created" });
|
||||
}
|
||||
catch (ValidationException vex)
|
||||
{
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Resource not found" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Responsibilities:**
|
||||
- ✅ Authenticate user
|
||||
- ✅ Extract user ID from claims
|
||||
- ✅ Map API DTO to Service DTO
|
||||
- ✅ Catch exceptions and return proper HTTP status codes
|
||||
- ❌ NO business logic
|
||||
- ❌ NO database access
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ **Service Layer** (`ContactService.cs`)
|
||||
|
||||
```csharp
|
||||
public class ContactService : IContactService
|
||||
{
|
||||
private readonly IContactDataService _dataService;
|
||||
private readonly IValidator<CreateContactDTO> _createValidator;
|
||||
|
||||
public ContactService(
|
||||
IContactDataService dataService,
|
||||
IValidator<CreateContactDTO> createValidator)
|
||||
{
|
||||
_dataService = dataService;
|
||||
_createValidator = createValidator;
|
||||
}
|
||||
|
||||
public async Task<ContactDTO> CreateContactAsync(CreateContactDTO dto, string userId)
|
||||
{
|
||||
// STEP 1: Validate using FluentValidation
|
||||
var validationResult = await _createValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
// STEP 2: Business logic (if any)
|
||||
// Example: Check for duplicates, apply business rules, etc.
|
||||
|
||||
// STEP 3: Map Service DTO → Data DTO
|
||||
var dataDto = new CreateContactDataDTO
|
||||
{
|
||||
FirstName = dto.FirstName,
|
||||
LastName = dto.LastName,
|
||||
Email = dto.Email,
|
||||
Phone = dto.Phone
|
||||
};
|
||||
|
||||
// STEP 4: Call Data Service Layer
|
||||
var createdContact = await _dataService.CreateAsync(dataDto, userId);
|
||||
|
||||
// STEP 5: Map Data DTO → Service DTO and return
|
||||
return new ContactDTO
|
||||
{
|
||||
Id = createdContact.Id,
|
||||
FirstName = createdContact.FirstName,
|
||||
LastName = createdContact.LastName,
|
||||
Email = createdContact.Email,
|
||||
Phone = createdContact.Phone
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Responsibilities:**
|
||||
- ✅ Validate input using FluentValidation
|
||||
- ✅ Apply business rules
|
||||
- ✅ Orchestrate operations
|
||||
- ✅ Transform data between layers
|
||||
- ❌ NO HTTP concerns
|
||||
- ❌ NO direct database access
|
||||
|
||||
---
|
||||
|
||||
### 3️⃣ **Data Service Layer** (`ContactDataService.cs`)
|
||||
|
||||
```csharp
|
||||
public class ContactDataService : IContactDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _db;
|
||||
|
||||
public ContactDataService(ApplicationDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public async Task<ContactDataDTO> CreateAsync(CreateContactDataDTO dto, string userId)
|
||||
{
|
||||
// STEP 1: Create entity from DTO
|
||||
var contact = new Contact
|
||||
{
|
||||
FirstName = dto.FirstName,
|
||||
LastName = dto.LastName,
|
||||
Email = dto.Email,
|
||||
Phone = dto.Phone,
|
||||
CreatedBy = userId,
|
||||
CreatedDate = DateTime.UtcNow,
|
||||
ModifiedBy = userId,
|
||||
ModifiedDate = DateTime.UtcNow
|
||||
};
|
||||
|
||||
// STEP 2: Add to database
|
||||
_db.Contacts.Add(contact);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
// STEP 3: Map entity → Data DTO and return
|
||||
return new ContactDataDTO
|
||||
{
|
||||
Id = contact.Id,
|
||||
FirstName = contact.FirstName,
|
||||
LastName = contact.LastName,
|
||||
Email = contact.Email,
|
||||
Phone = contact.Phone
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Responsibilities:**
|
||||
- ✅ Direct database operations (CRUD)
|
||||
- ✅ Entity mapping
|
||||
- ✅ Set audit fields (CreatedBy, CreatedDate, etc.)
|
||||
- ✅ Transaction management
|
||||
- ❌ NO validation
|
||||
- ❌ NO business logic
|
||||
|
||||
---
|
||||
|
||||
## 🔌 Dependency Injection (DI)
|
||||
|
||||
### Automatic Assembly Scanning
|
||||
|
||||
Services are **automatically registered** by scanning assemblies using naming convention.
|
||||
|
||||
**In `Program.cs`:**
|
||||
```csharp
|
||||
builder.Services.AddDataServices(); // Auto-register all Data Services
|
||||
builder.Services.AddBusinessServices(); // Auto-register all Business Services + Validators
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
**Module Files:**
|
||||
- `SeaHaven.DataServices/DependencyInjection/DataServicesModule.cs`
|
||||
- `SeaHaven.Services/DependencyInjection/ServicesModule.cs`
|
||||
|
||||
**Process:**
|
||||
1. Scans assembly for all classes
|
||||
2. Finds interface matching pattern `I{ClassName}`
|
||||
3. Registers as Scoped service
|
||||
4. Also auto-registers FluentValidation validators
|
||||
|
||||
### Naming Convention Required
|
||||
|
||||
| Interface | Implementation | Registered? |
|
||||
|-----------|---------------|-------------|
|
||||
| `IContactService` | `ContactService` | ✅ YES |
|
||||
| `IContactDataService` | `ContactDataService` | ✅ YES |
|
||||
| `ISomething` | `SomethingDifferent` | ❌ NO |
|
||||
|
||||
**Rule**: Interface must be `I` + Class name exactly
|
||||
|
||||
### Usage in Code
|
||||
|
||||
Once registered, inject via constructor:
|
||||
|
||||
```csharp
|
||||
public class ContactController : Controller
|
||||
{
|
||||
private readonly IContactService _contactService;
|
||||
|
||||
public ContactController(IContactService contactService)
|
||||
{
|
||||
_contactService = contactService;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Benefits
|
||||
|
||||
✅ No manual registration - just follow naming convention
|
||||
✅ Auto-discovery - new services work automatically
|
||||
✅ FluentValidation auto-registered
|
||||
|
||||
---
|
||||
|
||||
## ✅ FluentValidation
|
||||
|
||||
### Validator Example (`CreateContactDTOValidator.cs`)
|
||||
|
||||
```csharp
|
||||
public class CreateContactDTOValidator : AbstractValidator<CreateContactDTO>
|
||||
{
|
||||
public CreateContactDTOValidator()
|
||||
{
|
||||
RuleFor(x => x.FirstName)
|
||||
.NotEmpty().WithMessage("First name is required")
|
||||
.MaximumLength(50).WithMessage("First name cannot exceed 50 characters");
|
||||
|
||||
RuleFor(x => x.LastName)
|
||||
.NotEmpty().WithMessage("Last name is required")
|
||||
.MaximumLength(50).WithMessage("Last name cannot exceed 50 characters");
|
||||
|
||||
RuleFor(x => x.Email)
|
||||
.NotEmpty().WithMessage("Email is required")
|
||||
.EmailAddress().WithMessage("Invalid email format")
|
||||
.MaximumLength(100).WithMessage("Email cannot exceed 100 characters");
|
||||
|
||||
RuleFor(x => x.Phone)
|
||||
.Matches(@"^\+?[1-9]\d{1,14}$").When(x => !string.IsNullOrEmpty(x.Phone))
|
||||
.WithMessage("Invalid phone number format");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Where Validation Happens
|
||||
|
||||
```
|
||||
❌ Controller → NO detailed validation (only catches ValidationException)
|
||||
✅ Service → YES - FluentValidation runs here
|
||||
❌ DataService → NO validation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Interface Examples
|
||||
|
||||
### Service Interface (`IContactService.cs`)
|
||||
```csharp
|
||||
public interface IContactService
|
||||
{
|
||||
Task<ContactDTO> CreateContactAsync(CreateContactDTO dto, string userId);
|
||||
Task<ContactDTO> UpdateContactAsync(int id, UpdateContactDTO dto, string userId);
|
||||
Task DeleteContactAsync(int id, string userId);
|
||||
Task<ContactDTO> GetContactByIdAsync(int id);
|
||||
Task<PagedResult<ContactDTO>> GetContactsPagedAsync(int page, int pageSize, string? search);
|
||||
}
|
||||
```
|
||||
|
||||
### Data Service Interface (`IContactDataService.cs`)
|
||||
```csharp
|
||||
public interface IContactDataService
|
||||
{
|
||||
Task<ContactDataDTO> CreateAsync(CreateContactDataDTO dto, string userId);
|
||||
Task<ContactDataDTO> UpdateAsync(int id, UpdateContactDataDTO dto, string userId);
|
||||
Task DeleteAsync(int id, string userId);
|
||||
Task<ContactDataDTO?> GetByIdAsync(int id);
|
||||
Task<PagedResult<ContactDataDTO>> GetPagedAsync(int page, int pageSize, string? search);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 DTO Types
|
||||
|
||||
### 1. **API DTOs** (`Api.SeaHavenIndustries/DTOs/`)
|
||||
- Used for HTTP requests/responses
|
||||
- Map to frontend JSON structure
|
||||
- Example: `Contact_DTO`, `EditContact_DTO`
|
||||
|
||||
### 2. **Service DTOs** (`SeaHaven.Services/DTOs/`)
|
||||
- Used between Controller ↔ Service
|
||||
- Business-focused structure
|
||||
- Example: `CreateContactDTO`, `UpdateContactDTO`, `ContactDTO`
|
||||
|
||||
### 3. **Data DTOs** (`SeaHaven.DataServices/DTOs/`)
|
||||
- Used between Service ↔ DataService
|
||||
- Database-focused structure
|
||||
- Example: `CreateContactDataDTO`, `ContactDataDTO`
|
||||
|
||||
### DTO Mapping Flow
|
||||
```
|
||||
API DTO → Service DTO → Data DTO → Entity
|
||||
(HTTP) (Business) (Data) (Database)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Exception Handling Pattern
|
||||
|
||||
### Controller Exception Handling (STANDARD PATTERN)
|
||||
|
||||
```csharp
|
||||
[HttpPost("Create")]
|
||||
public async Task<IActionResult> Create([FromBody] SomeDTO dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get user ID
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
|
||||
// Call service
|
||||
await _service.CreateAsync(dto, userId);
|
||||
|
||||
return Ok(new Response { Status = "Success", Message = "Created successfully" });
|
||||
}
|
||||
catch (ValidationException vex) // ⚠️ CATCH THIS FIRST
|
||||
{
|
||||
var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
|
||||
return BadRequest(new Response { Status = "Validation Error", Message = errors });
|
||||
}
|
||||
catch (KeyNotFoundException) // ⚠️ THEN THIS
|
||||
{
|
||||
return NotFound(new Response { Status = "Error", Message = "Not found" });
|
||||
}
|
||||
catch (Exception ex) // ⚠️ GENERIC LAST
|
||||
{
|
||||
return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP Status Code Mapping
|
||||
- **200 OK** → Success
|
||||
- **400 Bad Request** → Validation errors
|
||||
- **401 Unauthorized** → Not authenticated
|
||||
- **404 Not Found** → Resource not found
|
||||
- **500 Internal Server Error** → Unexpected errors
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Authentication & User ID
|
||||
|
||||
### Getting User ID (Two Methods)
|
||||
|
||||
**Method 1: Using Claims (Preferred)**
|
||||
```csharp
|
||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
```
|
||||
|
||||
**Method 2: Using UserManager (for Identity)**
|
||||
```csharp
|
||||
var userId = _userManager.GetUserId(User);
|
||||
if (userId == null)
|
||||
return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
|
||||
```
|
||||
|
||||
### Audit Fields
|
||||
Every entity should track:
|
||||
- `CreatedBy` (string - User ID GUID)
|
||||
- `CreatedDate` (DateTime)
|
||||
- `ModifiedBy` (string - User ID GUID)
|
||||
- `ModifiedDate` (DateTime)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Naming Conventions
|
||||
|
||||
### Controllers
|
||||
- Named after entity + "Controller"
|
||||
- Example: `ContactController`, `EmployeeController`
|
||||
|
||||
### Services
|
||||
- Interface: `I[Entity]Service`
|
||||
- Implementation: `[Entity]Service`
|
||||
- Example: `IContactService`, `ContactService`
|
||||
|
||||
### Data Services
|
||||
- Interface: `I[Entity]DataService`
|
||||
- Implementation: `[Entity]DataService`
|
||||
- Example: `IContactDataService`, `ContactDataService`
|
||||
|
||||
### DTOs
|
||||
- API Layer: `[Entity]_DTO`, `Edit[Entity]_DTO`
|
||||
- Service Layer: `Create[Entity]DTO`, `Update[Entity]DTO`, `[Entity]DTO`
|
||||
- Data Layer: `Create[Entity]DataDTO`, `[Entity]DataDTO`
|
||||
|
||||
---
|
||||
|
||||
## ✅ Best Practices Checklist
|
||||
|
||||
### Controllers
|
||||
- [ ] Use dependency injection for services
|
||||
- [ ] Get user ID from `User.FindFirstValue(ClaimTypes.NameIdentifier)`
|
||||
- [ ] Return 401 if userId is null
|
||||
- [ ] Catch `ValidationException` BEFORE generic `Exception`
|
||||
- [ ] Return appropriate HTTP status codes
|
||||
- [ ] NO business logic in controllers
|
||||
- [ ] NO direct database access (`_db`)
|
||||
|
||||
### Services
|
||||
- [ ] Validate using FluentValidation
|
||||
- [ ] Throw `ValidationException` for validation errors
|
||||
- [ ] Throw `KeyNotFoundException` for not found
|
||||
- [ ] Pass userId to DataService
|
||||
- [ ] Transform DTOs between layers
|
||||
- [ ] Implement business rules here
|
||||
|
||||
### Data Services
|
||||
- [ ] Direct database access only
|
||||
- [ ] Set audit fields (CreatedBy, CreatedDate, ModifiedBy, ModifiedDate)
|
||||
- [ ] Use async/await for all database operations
|
||||
- [ ] NO validation logic
|
||||
- [ ] NO business logic
|
||||
|
||||
---
|
||||
|
||||
## 🎓 Quick Reference
|
||||
|
||||
| Layer | Purpose | Can Access | Cannot Access |
|
||||
|-------|---------|-----------|---------------|
|
||||
| **Controller** | HTTP handling, auth | Service layer | Database directly |
|
||||
| **Service** | Business logic, validation | DataService layer | Database directly |
|
||||
| **DataService** | Database operations | DbContext, Entities | HTTP context |
|
||||
| **Data** | Entity definitions | Nothing (POCOs) | Application logic |
|
||||
|
||||
---
|
||||
|
||||
## 📚 Example: Full CRUD Implementation
|
||||
|
||||
See these files for complete examples:
|
||||
- **Contact**: `ContactController.cs`, `ContactService.cs`, `ContactDataService.cs`
|
||||
- **Employee**: `EmployeeController.cs`, `EmployeeService.cs`, `EmployeeDataService.cs`
|
||||
- **Asset**: `AssetController.cs`, `AssetService.cs`, `AssetDataService.cs`
|
||||
|
||||
All follow the same Clean Architecture pattern!
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Configuration & Secrets
|
||||
|
||||
No secrets are stored in source. The committed `appsettings*.json` files hold only
|
||||
`${PLACEHOLDER}` tokens; real values must be supplied at runtime through one of the
|
||||
standard .NET configuration sources (later sources win):
|
||||
|
||||
1. `appsettings.json` / `appsettings.{Environment}.json` — committed, placeholders only.
|
||||
2. **User Secrets** (local dev): `dotnet user-secrets set "Section:Key" "value"`.
|
||||
3. **Environment variables** — use `__` (double underscore) as the section separator.
|
||||
In AWS Elastic Beanstalk these are the environment properties.
|
||||
|
||||
See `.env.example` in the repo root for the full list. Required values:
|
||||
|
||||
| Setting | Env-var form | Used by | Notes |
|
||||
|---|---|---|---|
|
||||
| `ConnectionStrings:DefaultConnection` | `ConnectionStrings__DefaultConnection` | Both projects | SQL Server connection string |
|
||||
| `SendGrid:ApiKey` | `SendGrid__ApiKey` | Both projects | Transactional email |
|
||||
| `JWT:Secret` | `JWT__Secret` | `Api.SeaHavenIndustries` | JWT signing key |
|
||||
| `GoogleMaps:ApiKey` | `GoogleMaps__ApiKey` | `SeaHavenIndustries` (Blazor) | Maps/Places; read in `App.razor` + `Home.razor` |
|
||||
|
||||
### AWS credentials
|
||||
|
||||
`UploadFileHp` (S3 uploads) uses the **AWS SDK default credential chain** — it no longer
|
||||
hardcodes access keys. Credentials resolve from `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`
|
||||
env vars, the shared profile/SSO, or (preferred in production) the EC2/ECS instance role.
|
||||
The region comes from `AddDefaultAWSOptions` in `Program.cs`.
|
||||
|
||||
> ⚠️ The secrets previously committed to this repo (DB password, SendGrid key, JWT secret,
|
||||
> Google Maps keys, AWS access key) should be considered compromised and **rotated**.
|
||||
|
||||
|
|
@ -66,6 +66,7 @@ namespace Data.SeaHavenIndustries
|
|||
public DbSet<FollowUps> FollowUps { get; set; }
|
||||
public DbSet<PMSchedules> PMSchedules { get; set; }
|
||||
public DbSet<ContactDetails> ContactDetails { get; set; }
|
||||
public DbSet<Events> Events { get; set; }
|
||||
|
||||
// Employee related DbSets
|
||||
public DbSet<EmployeeAddress> EmployeeAddresses { get; set; }
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
92
Data.SeaHavenIndustries/Enums/WorkOrderEnums.cs
Normal file
92
Data.SeaHavenIndustries/Enums/WorkOrderEnums.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
namespace Data.SeaHavenIndustries.Enums
|
||||
{
|
||||
public enum WorkOrderStatus
|
||||
{
|
||||
Open = 1,
|
||||
InProgress = 2,
|
||||
Completed = 3,
|
||||
Cancelled = 4,
|
||||
OnHold = 5
|
||||
}
|
||||
|
||||
public enum WorkOrderPriority
|
||||
{
|
||||
Low = 1,
|
||||
Medium = 2,
|
||||
High = 3,
|
||||
Critical = 4
|
||||
}
|
||||
|
||||
public enum AuditAction
|
||||
{
|
||||
Create = 1,
|
||||
Edit = 2,
|
||||
Delete = 3,
|
||||
StatusChange = 4,
|
||||
AssignmentChange = 5,
|
||||
Dispatch = 6,
|
||||
DispatchUpdate = 7,
|
||||
DispatchVerify = 8
|
||||
}
|
||||
|
||||
public enum ContactType
|
||||
{
|
||||
Primary = 1,
|
||||
Secondary = 2,
|
||||
Billing = 3,
|
||||
Technical = 4
|
||||
}
|
||||
|
||||
public enum AssetStatus
|
||||
{
|
||||
Active = 1,
|
||||
Inactive = 2,
|
||||
UnderMaintenance = 3,
|
||||
Retired = 4
|
||||
}
|
||||
|
||||
public enum DispatchStatus
|
||||
{
|
||||
Pending = 1,
|
||||
Assigned = 2,
|
||||
InProgress = 3,
|
||||
Completed = 4,
|
||||
Cancelled = 5
|
||||
}
|
||||
|
||||
public enum FollowUpStatus
|
||||
{
|
||||
Open = 1,
|
||||
InProgress = 2,
|
||||
Resolved = 3,
|
||||
Closed = 4
|
||||
}
|
||||
|
||||
public enum PMScheduleFrequency
|
||||
{
|
||||
Daily = 1,
|
||||
Weekly = 2,
|
||||
Monthly = 3,
|
||||
Quarterly = 4,
|
||||
SemiAnnual = 5,
|
||||
Annual = 6
|
||||
}
|
||||
|
||||
public enum CommentType
|
||||
{
|
||||
General = 1,
|
||||
Internal = 2,
|
||||
Customer = 3,
|
||||
Vendor = 4,
|
||||
System = 5
|
||||
}
|
||||
|
||||
public enum RecordType
|
||||
{
|
||||
WorkOrder = 1,
|
||||
Dispatch = 2,
|
||||
Quote = 3,
|
||||
Contact = 4,
|
||||
Asset = 5
|
||||
}
|
||||
}
|
||||
|
|
@ -86,8 +86,9 @@ namespace Data.SeaHavenIndustries
|
|||
public Employee Employee { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class EmployeeEmail : FullAuditEntity
|
||||
public class EmployeeEmail
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int EmployeeId { get; set; }
|
||||
public Employee? Employee { get; set; }
|
||||
|
||||
|
|
@ -99,22 +100,28 @@ namespace Data.SeaHavenIndustries
|
|||
public bool? IsPrimary { get; set; }
|
||||
}
|
||||
|
||||
public class Department : FullAuditEntity
|
||||
public class Department
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[MaxLength(200)]
|
||||
public string Name { get; set; } = null!;
|
||||
public ICollection<Employee>? Employees { get; set; }
|
||||
}
|
||||
|
||||
public class JobTitle : FullAuditEntity
|
||||
public class JobTitle
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[MaxLength(200)]
|
||||
public string Name { get; set; } = null!;
|
||||
public ICollection<Employee>? Employees { get; set; }
|
||||
}
|
||||
|
||||
public class Region : FullAuditEntity
|
||||
public class Region
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[MaxLength(200)]
|
||||
public string Name { get; set; } = null!;
|
||||
public ICollection<Employee>? Employees { get; set; }
|
||||
|
|
|
|||
49
Data.SeaHavenIndustries/Models/Events.cs
Normal file
49
Data.SeaHavenIndustries/Models/Events.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Data.SeaHavenIndustries
|
||||
{
|
||||
/// <summary>
|
||||
/// Events entity for Calendar/Events module
|
||||
/// Tracks calendar events, appointments, meetings, etc.
|
||||
/// </summary>
|
||||
public class Events : FullAuditEntity
|
||||
{
|
||||
[Required]
|
||||
[StringLength(100)]
|
||||
public string Title { get; set; } = null!;
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
[StringLength(150)]
|
||||
public string? Location { get; set; }
|
||||
|
||||
// Related Record (can link to Accounts, Contacts, Work Orders, etc.)
|
||||
public int? RecordID { get; set; }
|
||||
public string? RecordName { get; set; }
|
||||
public string? RecordTargetType { get; set; }
|
||||
|
||||
// Reminder Settings
|
||||
public int ReminderMinutes { get; set; } = 15; // Default: 15 minutes before
|
||||
|
||||
// Color for calendar display
|
||||
[StringLength(20)]
|
||||
public string EventColor { get; set; } = "primary"; // primary, success, info, warning, danger
|
||||
|
||||
// Schedule
|
||||
[Required]
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
[StringLength(10)]
|
||||
public string? StartTime { get; set; } // e.g., "08:00 AM"
|
||||
|
||||
[Required]
|
||||
public DateTime EndDate { get; set; }
|
||||
|
||||
[StringLength(10)]
|
||||
public string? EndTime { get; set; } // e.g., "08:30 AM"
|
||||
|
||||
public bool AllDay { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6,21 +6,25 @@ namespace Data.SeaHavenIndustries
|
|||
{
|
||||
public class Locations : FullAuditEntity
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? Name { 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? Contact { get; set; }
|
||||
public string? ContactEmail { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public string? Latitude { get; set; }
|
||||
public string? Longitude { get; set; }
|
||||
public int? AccountId { get; set; }
|
||||
[ForeignKey("AccountId")]
|
||||
public Accounts? Account { get; set; }
|
||||
|
||||
// Address fields - match actual database columns
|
||||
public string? Address1 { get; set; }
|
||||
public string? Address2 { get; set; }
|
||||
public string? City { get; set; }
|
||||
public string? State { get; set; }
|
||||
public string? Zip { get; set; }
|
||||
|
||||
// Contact fields - match actual database columns
|
||||
public string? PhoneNumber { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Status { get; set; }
|
||||
|
||||
// Navigation Properties
|
||||
public ICollection<Template>? Templates { get; set; }
|
||||
public ICollection<WorkOrder>? workOrders { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SeaHaven.DataServices.DependencyInjection
|
||||
{
|
||||
public static class DataServicesModule
|
||||
{
|
||||
private static readonly List<Type> ExcludedTypes = new();
|
||||
|
||||
public static IServiceCollection AddDataServices(this IServiceCollection services)
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
var allClasses = assembly.GetTypes()
|
||||
.Where(t => t.IsClass &&
|
||||
!t.IsAbstract &&
|
||||
!t.IsGenericType &&
|
||||
!ExcludedTypes.Contains(t))
|
||||
.ToList();
|
||||
|
||||
foreach (var implementationType in allClasses)
|
||||
{
|
||||
var defaultInterface = implementationType.GetInterfaces()
|
||||
.FirstOrDefault(i => i.Name == $"I{implementationType.Name}");
|
||||
|
||||
if (defaultInterface != null)
|
||||
{
|
||||
services.AddScoped(defaultInterface, implementationType);
|
||||
}
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
100
SeaHaven.DataServices/Implementation/AccountDataService.cs
Normal file
100
SeaHaven.DataServices/Implementation/AccountDataService.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.DataServices.Implementation
|
||||
{
|
||||
public class AccountDataService : IAccountDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public AccountDataService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Accounts?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.Accounts.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<Accounts?> GetByIdWithDetailsAsync(int id)
|
||||
{
|
||||
return await _context.Accounts
|
||||
.Include(a => a.Contacts)
|
||||
.Include(a => a.Addresses)
|
||||
.FirstOrDefaultAsync(a => a.Id == id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Accounts>> GetAllAsync()
|
||||
{
|
||||
return await _context.Accounts.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<Accounts> Items, int TotalCount)> GetPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null)
|
||||
{
|
||||
search ??= "";
|
||||
|
||||
var query = _context.Accounts
|
||||
.Where(a => a.IsDeleted == false || a.IsDeleted == null);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var s = search.ToLower();
|
||||
query = query.Where(a =>
|
||||
(a.Name ?? "").ToLower().Contains(s) ||
|
||||
(a.Email ?? "").ToLower().Contains(s) ||
|
||||
(a.Phone ?? "").Contains(s) ||
|
||||
(a.Website ?? "").ToLower().Contains(s)
|
||||
);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderByDescending(a => a.Id)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return (items, totalCount);
|
||||
}
|
||||
|
||||
public async Task<Accounts> AddAsync(Accounts account)
|
||||
{
|
||||
account.CreatedDate = DateTime.Now;
|
||||
await _context.Accounts.AddAsync(account);
|
||||
await _context.SaveChangesAsync();
|
||||
return account;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Accounts account)
|
||||
{
|
||||
account.LastModificationTime = DateTime.Now;
|
||||
_context.Accounts.Update(account);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity != null)
|
||||
{
|
||||
_context.Accounts.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(int id)
|
||||
{
|
||||
return await _context.Accounts.AnyAsync(a => a.Id == id);
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync()
|
||||
{
|
||||
return await _context.Accounts.CountAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
108
SeaHaven.DataServices/Implementation/AssetDataService.cs
Normal file
108
SeaHaven.DataServices/Implementation/AssetDataService.cs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.DataServices.Implementation
|
||||
{
|
||||
public class AssetDataService : IAssetDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public AssetDataService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Assets?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.Assets.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<Assets?> GetByIdWithDetailsAsync(int id)
|
||||
{
|
||||
return await _context.Assets
|
||||
.Include(a => a.Account)
|
||||
.Include(a => a.Location)
|
||||
.FirstOrDefaultAsync(a => a.Id == id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Assets>> GetAllAsync()
|
||||
{
|
||||
return await _context.Assets.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Assets>> GetByLocationIdAsync(int locationId)
|
||||
{
|
||||
return await _context.Assets
|
||||
.Where(a => a.LocationId == locationId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<Assets> Items, int TotalCount)> GetPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null)
|
||||
{
|
||||
search ??= "";
|
||||
|
||||
var query = _context.Assets
|
||||
.Include(a => a.Account)
|
||||
.Include(a => a.Location)
|
||||
.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var s = search.ToLower();
|
||||
query = query.Where(a =>
|
||||
(a.Name != null && a.Name.ToLower().Contains(s)) ||
|
||||
(a.AssetCode != null && a.AssetCode.ToLower().Contains(s)) ||
|
||||
(a.Description != null && a.Description.ToLower().Contains(s))
|
||||
);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderByDescending(a => a.Id)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return (items, totalCount);
|
||||
}
|
||||
|
||||
public async Task<Assets> AddAsync(Assets asset)
|
||||
{
|
||||
asset.CreatedDate = DateTime.Now;
|
||||
await _context.Assets.AddAsync(asset);
|
||||
await _context.SaveChangesAsync();
|
||||
return asset;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Assets asset)
|
||||
{
|
||||
asset.LastModificationTime = DateTime.Now;
|
||||
_context.Assets.Update(asset);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity != null)
|
||||
{
|
||||
_context.Assets.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(int id)
|
||||
{
|
||||
return await _context.Assets.AnyAsync(a => a.Id == id);
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync()
|
||||
{
|
||||
return await _context.Assets.CountAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
56
SeaHaven.DataServices/Implementation/CategoryDataService.cs
Normal file
56
SeaHaven.DataServices/Implementation/CategoryDataService.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.DataServices.Implementation
|
||||
{
|
||||
public class CategoryDataService : ICategoryDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public CategoryDataService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Category?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.Categories.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Category>> GetAllAsync()
|
||||
{
|
||||
return await _context.Categories.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Category> AddAsync(Category category)
|
||||
{
|
||||
category.CreatedDate = DateTime.Now;
|
||||
await _context.Categories.AddAsync(category);
|
||||
await _context.SaveChangesAsync();
|
||||
return category;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Category category)
|
||||
{
|
||||
category.LastModificationTime = DateTime.Now;
|
||||
_context.Categories.Update(category);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity != null)
|
||||
{
|
||||
_context.Categories.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(int id)
|
||||
{
|
||||
return await _context.Categories.AnyAsync(c => c.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
SeaHaven.DataServices/Implementation/CommentDataService.cs
Normal file
75
SeaHaven.DataServices/Implementation/CommentDataService.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.DataServices.Implementation
|
||||
{
|
||||
public class CommentDataService : ICommentDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public CommentDataService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Comments?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.Comments.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Comments>> GetAllAsync()
|
||||
{
|
||||
return await _context.Comments
|
||||
.Include(c => c.ApplicationUser)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Comments>> GetByWorkOrderIdAsync(int workOrderId)
|
||||
{
|
||||
return await _context.Comments
|
||||
.Where(c => c.WorkerOrderId == workOrderId)
|
||||
.Include(c => c.ApplicationUser)
|
||||
.OrderBy(c => c.CreatedDate)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Comments>> GetByDispatchIdAsync(int dispatchId)
|
||||
{
|
||||
return await _context.Comments
|
||||
.Where(c => c.DispatchId == dispatchId)
|
||||
.Include(c => c.ApplicationUser)
|
||||
.OrderBy(c => c.CreatedDate)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Comments> AddAsync(Comments comment)
|
||||
{
|
||||
comment.CreatedDate = DateTime.UtcNow;
|
||||
await _context.Comments.AddAsync(comment);
|
||||
await _context.SaveChangesAsync();
|
||||
return comment;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Comments comment)
|
||||
{
|
||||
_context.Comments.Update(comment);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity != null)
|
||||
{
|
||||
_context.Comments.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(int id)
|
||||
{
|
||||
return await _context.Comments.AnyAsync(c => c.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
160
SeaHaven.DataServices/Implementation/ContactDataService.cs
Normal file
160
SeaHaven.DataServices/Implementation/ContactDataService.cs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.DataServices.Implementation
|
||||
{
|
||||
public class ContactDataService : IContactDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public ContactDataService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Contacts?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.Contacts.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<Contacts?> GetByIdWithDetailsAsync(int id)
|
||||
{
|
||||
return await _context.Contacts
|
||||
.Include(c => c.Location)
|
||||
.Include(c => c.Account)
|
||||
.FirstOrDefaultAsync(c => c.Id == id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Contacts>> GetAllAsync()
|
||||
{
|
||||
return await _context.Contacts.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Contacts>> GetByLocationIdAsync(int locationId)
|
||||
{
|
||||
return await _context.Contacts
|
||||
.Where(c => c.LocationId == locationId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Contacts>> GetByAccountIdAsync(int accountId)
|
||||
{
|
||||
return await _context.Contacts
|
||||
.Where(c => c.AccountId == accountId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Contacts>> GetByTypeAsync(string type)
|
||||
{
|
||||
return await _context.Contacts
|
||||
.Where(c => c.ContactType == type)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Contacts?> GetByEmailAsync(string email)
|
||||
{
|
||||
return await _context.Contacts
|
||||
.FirstOrDefaultAsync(c => c.Email == email);
|
||||
}
|
||||
|
||||
public async Task<Contacts?> GetByPhoneAsync(string phone)
|
||||
{
|
||||
return await _context.Contacts
|
||||
.FirstOrDefaultAsync(c => c.PhoneNumber == phone);
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<Contacts> Items, int TotalCount)> GetPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null,
|
||||
string? type = null)
|
||||
{
|
||||
search ??= "";
|
||||
var query = _context.Contacts.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var s = search.ToLower();
|
||||
query = query.Where(c =>
|
||||
(c.FirstName != null && c.FirstName.ToLower().Contains(s)) ||
|
||||
(c.LastName != null && c.LastName.ToLower().Contains(s)) ||
|
||||
(c.Email != null && c.Email.ToLower().Contains(s)) ||
|
||||
(c.PhoneNumber != null && c.PhoneNumber.Contains(s)));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(type))
|
||||
{
|
||||
query = query.Where(c => c.ContactType == type);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderByDescending(c => c.Id)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return (items, totalCount);
|
||||
}
|
||||
|
||||
public async Task<Contacts> AddAsync(Contacts contact)
|
||||
{
|
||||
contact.CreatedDate = DateTime.Now;
|
||||
await _context.Contacts.AddAsync(contact);
|
||||
await _context.SaveChangesAsync();
|
||||
return contact;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Contacts contact)
|
||||
{
|
||||
contact.LastModificationTime = DateTime.Now;
|
||||
_context.Contacts.Update(contact);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity != null)
|
||||
{
|
||||
_context.Contacts.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(int id)
|
||||
{
|
||||
return await _context.Contacts.AnyAsync(c => c.Id == id);
|
||||
}
|
||||
|
||||
public async Task<bool> EmailExistsAsync(string email, int? excludeId = null)
|
||||
{
|
||||
return await _context.Contacts.AnyAsync(c =>
|
||||
c.Email == email &&
|
||||
(excludeId == null || c.Id != excludeId));
|
||||
}
|
||||
|
||||
public async Task<bool> PhoneExistsAsync(string phone, int? excludeId = null)
|
||||
{
|
||||
return await _context.Contacts.AnyAsync(c =>
|
||||
c.PhoneNumber == phone &&
|
||||
(excludeId == null || c.Id != excludeId));
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync()
|
||||
{
|
||||
return await _context.Contacts.CountAsync();
|
||||
}
|
||||
|
||||
public async Task<int> CountByAccountIdAsync(int accountId)
|
||||
{
|
||||
return await _context.Contacts.CountAsync(c => c.AccountId == accountId);
|
||||
}
|
||||
|
||||
public async Task<int> CountByTypeAsync(string type)
|
||||
{
|
||||
return await _context.Contacts.CountAsync(c => c.ContactType == type);
|
||||
}
|
||||
}
|
||||
}
|
||||
220
SeaHaven.DataServices/Implementation/DispatchDataService.cs
Normal file
220
SeaHaven.DataServices/Implementation/DispatchDataService.cs
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.DataServices.Implementation
|
||||
{
|
||||
public class DispatchDataService : IDispatchDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public DispatchDataService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Dispatch?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.Dispatches.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<Dispatch?> GetByIdWithDetailsAsync(int id)
|
||||
{
|
||||
return await _context.Dispatches
|
||||
.Include(d => d.Vendor)
|
||||
.FirstOrDefaultAsync(d => d.Id == id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Dispatch>> GetAllAsync()
|
||||
{
|
||||
return await _context.Dispatches.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Dispatch>> GetByVendorIdAsync(int vendorId)
|
||||
{
|
||||
return await _context.Dispatches
|
||||
.Where(d => d.VendorId == vendorId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Dispatch>> GetByStatusAsync(string status)
|
||||
{
|
||||
return await _context.Dispatches
|
||||
.Where(d => d.Status == status)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<Dispatch> Items, int TotalCount)> GetPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null,
|
||||
string? status = null)
|
||||
{
|
||||
var query = _context.Dispatches.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
query = query.Where(d => d.DispatchNumber!.Contains(search));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(status))
|
||||
{
|
||||
query = query.Where(d => d.Status == status);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderByDescending(d => d.CreatedDate)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return (items, totalCount);
|
||||
}
|
||||
|
||||
public async Task<Dispatch> AddAsync(Dispatch dispatch)
|
||||
{
|
||||
dispatch.CreatedDate = DateTime.Now;
|
||||
await _context.Dispatches.AddAsync(dispatch);
|
||||
await _context.SaveChangesAsync();
|
||||
return dispatch;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Dispatch dispatch)
|
||||
{
|
||||
dispatch.LastModificationTime = DateTime.Now;
|
||||
_context.Dispatches.Update(dispatch);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity != null)
|
||||
{
|
||||
_context.Dispatches.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(int id)
|
||||
{
|
||||
return await _context.Dispatches.AnyAsync(d => d.Id == id);
|
||||
}
|
||||
|
||||
public async Task<string> GenerateNextDispatchNumberAsync()
|
||||
{
|
||||
return await Task.FromResult($"DISP-{DateTime.Now:yyyyMMdd}-{new Random().Next(1000, 9999)}");
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync()
|
||||
{
|
||||
return await _context.Dispatches.CountAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Dispatch>> GetByWorkOrderIdAsync(int workOrderId)
|
||||
{
|
||||
var dispatchIds = await _context.DispatchWorkOrders
|
||||
.Where(dwo => dwo.WorkOrderId == workOrderId)
|
||||
.Select(dwo => dwo.DispatchId)
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
|
||||
var directIds = await _context.Dispatches
|
||||
.Where(d => d.WorkOrderId == workOrderId)
|
||||
.Select(d => d.Id)
|
||||
.ToListAsync();
|
||||
|
||||
var allIds = dispatchIds.Union(directIds).Distinct().ToList();
|
||||
|
||||
return await _context.Dispatches
|
||||
.Where(d => allIds.Contains(d.Id))
|
||||
.Include(d => d.Vendor)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Dispatch>> GetByWorkOrderIdsAsync(IEnumerable<int> workOrderIds)
|
||||
{
|
||||
return await _context.Dispatches
|
||||
.Where(d => workOrderIds.Contains(d.WorkOrderId ?? 0))
|
||||
.Include(d => d.Vendor)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<string> GenerateNextPONumberAsync()
|
||||
{
|
||||
var lastPO = await _context.Dispatches
|
||||
.Where(d => d.PONumber != null)
|
||||
.OrderByDescending(d => d.PONumber)
|
||||
.Select(d => d.PONumber)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
int nextPO = 1000;
|
||||
if (!string.IsNullOrEmpty(lastPO) && int.TryParse(lastPO, out int lastNum))
|
||||
{
|
||||
nextPO = lastNum + 1;
|
||||
}
|
||||
|
||||
return nextPO.ToString();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DispatchChecklistItem>> GetChecklistItemsAsync(int dispatchId)
|
||||
{
|
||||
return await _context.DispatchChecklistItems
|
||||
.Where(ci => ci.DispatchId == dispatchId)
|
||||
.OrderBy(ci => ci.SortOrder)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<DispatchChecklistItem?> GetChecklistItemByIdAsync(int id)
|
||||
{
|
||||
return await _context.DispatchChecklistItems.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task AddChecklistItemAsync(DispatchChecklistItem item)
|
||||
{
|
||||
await _context.DispatchChecklistItems.AddAsync(item);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task UpdateChecklistItemAsync(DispatchChecklistItem item)
|
||||
{
|
||||
_context.DispatchChecklistItems.Update(item);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DispatchSignoff>> GetSignoffsAsync(int dispatchId)
|
||||
{
|
||||
return await _context.DispatchSignoffs
|
||||
.Where(s => s.DispatchId == dispatchId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task AddSignoffAsync(DispatchSignoff signoff)
|
||||
{
|
||||
signoff.SignedAt = DateTime.UtcNow;
|
||||
await _context.DispatchSignoffs.AddAsync(signoff);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> HasSignoffTypeAsync(int dispatchId, string signoffType)
|
||||
{
|
||||
return await _context.DispatchSignoffs
|
||||
.AnyAsync(s => s.DispatchId == dispatchId && s.SignoffType == signoffType);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DispatchWorkOrder>> GetDispatchWorkOrdersAsync(int dispatchId)
|
||||
{
|
||||
return await _context.DispatchWorkOrders
|
||||
.Where(dwo => dwo.DispatchId == dispatchId)
|
||||
.Include(dwo => dwo.WorkOrder)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task AddDispatchWorkOrderAsync(DispatchWorkOrder dispatchWorkOrder)
|
||||
{
|
||||
await _context.DispatchWorkOrders.AddAsync(dispatchWorkOrder);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
108
SeaHaven.DataServices/Implementation/EmployeeDataService.cs
Normal file
108
SeaHaven.DataServices/Implementation/EmployeeDataService.cs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.DataServices.Implementation
|
||||
{
|
||||
public class EmployeeDataService : IEmployeeDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public EmployeeDataService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Employee?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.Employees.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<Employee?> GetByIdWithDetailsAsync(int id)
|
||||
{
|
||||
return await _context.Employees
|
||||
.Include(e => e.Department)
|
||||
.Include(e => e.JobTitle)
|
||||
.Include(e => e.Region)
|
||||
.Include(e => e.Emails)
|
||||
.Include(e => e.Phones)
|
||||
.FirstOrDefaultAsync(e => e.Id == id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Employee>> GetAllAsync()
|
||||
{
|
||||
return await _context.Employees.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<Employee> Items, int TotalCount)> GetPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null)
|
||||
{
|
||||
search ??= "";
|
||||
|
||||
var query = _context.Employees
|
||||
.Include(e => e.JobTitle)
|
||||
.Include(e => e.Department)
|
||||
.Include(e => e.Region)
|
||||
.Include(e => e.Emails)
|
||||
.Include(e => e.Phones)
|
||||
.Where(e => !e.IsDeleted.HasValue || e.IsDeleted == false);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var s = search.ToLower();
|
||||
query = query.Where(e =>
|
||||
(e.FirstName != null && e.FirstName.ToLower().Contains(s)) ||
|
||||
(e.LastName != null && e.LastName.ToLower().Contains(s)) ||
|
||||
e.Emails.Any(x => x.Address != null && x.Address.ToLower().Contains(s)) ||
|
||||
e.Phones.Any(x => x.Number != null && x.Number.Contains(search))
|
||||
);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderByDescending(e => e.Id)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return (items, totalCount);
|
||||
}
|
||||
|
||||
public async Task<Employee> AddAsync(Employee employee)
|
||||
{
|
||||
employee.CreatedDate = DateTime.Now;
|
||||
await _context.Employees.AddAsync(employee);
|
||||
await _context.SaveChangesAsync();
|
||||
return employee;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Employee employee)
|
||||
{
|
||||
employee.LastModificationTime = DateTime.Now;
|
||||
_context.Employees.Update(employee);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity != null)
|
||||
{
|
||||
_context.Employees.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(int id)
|
||||
{
|
||||
return await _context.Employees.AnyAsync(e => e.Id == id);
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync()
|
||||
{
|
||||
return await _context.Employees.CountAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
124
SeaHaven.DataServices/Implementation/FollowUpDataService.cs
Normal file
124
SeaHaven.DataServices/Implementation/FollowUpDataService.cs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.DataServices.Implementation
|
||||
{
|
||||
public class FollowUpDataService : IFollowUpDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public FollowUpDataService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<FollowUps?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.FollowUps.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<FollowUps?> GetByIdWithDetailsAsync(int id)
|
||||
{
|
||||
return await _context.FollowUps
|
||||
.Include(x => x.Employee)
|
||||
.Include(x => x.Account)
|
||||
.Include(x => x.Location)
|
||||
// Removed .Include(x => x.WorkOrder) due to schema mismatch - WorkOrder has columns not in DB
|
||||
.FirstOrDefaultAsync(f => f.Id == id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<FollowUps>> GetAllAsync()
|
||||
{
|
||||
return await _context.FollowUps.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<FollowUps>> GetByWorkOrderIdAsync(int workOrderId)
|
||||
{
|
||||
return await _context.FollowUps
|
||||
.Where(f => f.WorkOrderId == workOrderId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<FollowUps>> GetByUserIdAsync(string userId)
|
||||
{
|
||||
return await _context.FollowUps
|
||||
.Where(f => f.createdby == userId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<FollowUps> Items, int TotalCount)> GetPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null,
|
||||
string? status = null)
|
||||
{
|
||||
search ??= "";
|
||||
status = string.IsNullOrWhiteSpace(status) ? "Open" : status;
|
||||
|
||||
var query = _context.FollowUps
|
||||
.Include(x => x.Employee)
|
||||
.Include(x => x.Account)
|
||||
.Include(x => x.Location)
|
||||
// Removed .Include(x => x.WorkOrder) due to schema mismatch - WorkOrder has columns not in DB
|
||||
.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))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderByDescending(f => f.Id)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return (items, totalCount);
|
||||
}
|
||||
|
||||
public async Task<FollowUps> AddAsync(FollowUps followUp)
|
||||
{
|
||||
followUp.CreatedDate = DateTime.Now;
|
||||
await _context.FollowUps.AddAsync(followUp);
|
||||
await _context.SaveChangesAsync();
|
||||
return followUp;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(FollowUps followUp)
|
||||
{
|
||||
followUp.LastModificationTime = DateTime.Now;
|
||||
_context.FollowUps.Update(followUp);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity != null)
|
||||
{
|
||||
_context.FollowUps.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(int id)
|
||||
{
|
||||
return await _context.FollowUps.AnyAsync(f => f.Id == id);
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync()
|
||||
{
|
||||
return await _context.FollowUps.CountAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
132
SeaHaven.DataServices/Implementation/LocationDataService.cs
Normal file
132
SeaHaven.DataServices/Implementation/LocationDataService.cs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.DataServices.Implementation
|
||||
{
|
||||
public class LocationDataService : ILocationDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public LocationDataService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Locations?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.Locations.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<Locations?> GetByIdWithDetailsAsync(int id)
|
||||
{
|
||||
return await _context.Locations
|
||||
.FirstOrDefaultAsync(l => l.Id == id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Locations>> GetAllAsync()
|
||||
{
|
||||
return await _context.Locations.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Locations>> GetByAccountIdAsync(int accountId)
|
||||
{
|
||||
// AccountId doesn't exist in database - return all for now
|
||||
// TODO: Add AccountId column to database if needed
|
||||
return await _context.Locations.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<Locations> Items, int TotalCount)> GetPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null)
|
||||
{
|
||||
var query = _context.Locations.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
query = query.Where(l =>
|
||||
l.Name!.Contains(search) ||
|
||||
l.City!.Contains(search));
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderBy(l => l.Name)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return (items, totalCount);
|
||||
}
|
||||
|
||||
public async Task<Locations> AddAsync(Locations location)
|
||||
{
|
||||
location.CreatedDate = DateTime.Now;
|
||||
await _context.Locations.AddAsync(location);
|
||||
await _context.SaveChangesAsync();
|
||||
return location;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Locations location)
|
||||
{
|
||||
location.LastModificationTime = DateTime.Now;
|
||||
_context.Locations.Update(location);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity != null)
|
||||
{
|
||||
_context.Locations.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(int id)
|
||||
{
|
||||
return await _context.Locations.AnyAsync(l => l.Id == id);
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync()
|
||||
{
|
||||
return await _context.Locations.CountAsync();
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<object> Items, int TotalCount)> GetAddressbookPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null)
|
||||
{
|
||||
search ??= "";
|
||||
|
||||
var query = _context.Locations.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var s = search.ToLower();
|
||||
query = query.Where(w =>
|
||||
(w.Name ?? "").ToLower().Contains(s) ||
|
||||
(w.Title ?? "").ToLower().Contains(s));
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var data = await query
|
||||
.Select(n => new
|
||||
{
|
||||
n.Id,
|
||||
n.Name,
|
||||
n.Title
|
||||
})
|
||||
.OrderByDescending(d => d.Id)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return (data, totalCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
SeaHaven.DataServices/Implementation/PMScheduleDataService.cs
Normal file
100
SeaHaven.DataServices/Implementation/PMScheduleDataService.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.DataServices.Implementation
|
||||
{
|
||||
public class PMScheduleDataService : IPMScheduleDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public PMScheduleDataService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<PMSchedules?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.PMSchedules.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<PMSchedules?> GetByIdWithDetailsAsync(int id)
|
||||
{
|
||||
return await _context.PMSchedules
|
||||
.FirstOrDefaultAsync(p => p.Id == id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<PMSchedules>> GetAllAsync()
|
||||
{
|
||||
return await _context.PMSchedules.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<PMSchedules>> GetAllWithDetailsAsync()
|
||||
{
|
||||
return await _context.PMSchedules
|
||||
.Include(x => x.Asset)
|
||||
.Include(x => x.Location)
|
||||
.OrderByDescending(x => x.Id)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<PMSchedules>> GetByLocationIdAsync(int locationId)
|
||||
{
|
||||
return await _context.PMSchedules
|
||||
.Where(p => p.LocationId == locationId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<PMSchedules> Items, int TotalCount)> GetPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null)
|
||||
{
|
||||
var query = _context.PMSchedules.AsQueryable();
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderBy(p => p.Id)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return (items, totalCount);
|
||||
}
|
||||
|
||||
public async Task<PMSchedules> AddAsync(PMSchedules schedule)
|
||||
{
|
||||
schedule.CreatedDate = DateTime.Now;
|
||||
await _context.PMSchedules.AddAsync(schedule);
|
||||
await _context.SaveChangesAsync();
|
||||
return schedule;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(PMSchedules schedule)
|
||||
{
|
||||
schedule.LastModificationTime = DateTime.Now;
|
||||
_context.PMSchedules.Update(schedule);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity != null)
|
||||
{
|
||||
_context.PMSchedules.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(int id)
|
||||
{
|
||||
return await _context.PMSchedules.AnyAsync(p => p.Id == id);
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync()
|
||||
{
|
||||
return await _context.PMSchedules.CountAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
75
SeaHaven.DataServices/Implementation/QuotesDataService.cs
Normal file
75
SeaHaven.DataServices/Implementation/QuotesDataService.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.DataServices.Implementation
|
||||
{
|
||||
public class QuotesDataService : IQuotesDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public QuotesDataService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Quotes?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.Quotes.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Quotes>> GetAllAsync()
|
||||
{
|
||||
return await _context.Quotes.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Quotes>> GetByWorkOrderIdAsync(int workOrderId)
|
||||
{
|
||||
return await _context.Quotes
|
||||
.Where(q => q.WorkorderId == workOrderId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Quotes> AddAsync(Quotes quote)
|
||||
{
|
||||
await _context.Quotes.AddAsync(quote);
|
||||
await _context.SaveChangesAsync();
|
||||
return quote;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Quotes quote)
|
||||
{
|
||||
_context.Quotes.Update(quote);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity != null)
|
||||
{
|
||||
_context.Quotes.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(int id)
|
||||
{
|
||||
return await _context.Quotes.AnyAsync(q => q.Id == id);
|
||||
}
|
||||
|
||||
public async Task UnlinkFromWorkOrderAsync(int workOrderId)
|
||||
{
|
||||
var quotes = await _context.Quotes
|
||||
.Where(q => q.WorkorderId == workOrderId)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var quote in quotes)
|
||||
{
|
||||
quote.WorkorderId = null;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.DataServices.Implementation
|
||||
{
|
||||
public class TaskListTemplateDataService : ITaskListTemplateDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public TaskListTemplateDataService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<TaskListTemplate?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.TaskListTemplates.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<TaskListTemplate?> GetByIdWithItemsAsync(int id)
|
||||
{
|
||||
return await _context.TaskListTemplates
|
||||
.Include(t => t.Items)
|
||||
.FirstOrDefaultAsync(t => t.Id == id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TaskListTemplate>> GetAllAsync()
|
||||
{
|
||||
return await _context.TaskListTemplates.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<TaskListTemplate> AddAsync(TaskListTemplate template)
|
||||
{
|
||||
await _context.TaskListTemplates.AddAsync(template);
|
||||
await _context.SaveChangesAsync();
|
||||
return template;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(TaskListTemplate template)
|
||||
{
|
||||
_context.TaskListTemplates.Update(template);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity != null)
|
||||
{
|
||||
_context.TaskListTemplates.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(int id)
|
||||
{
|
||||
return await _context.TaskListTemplates.AnyAsync(t => t.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
SeaHaven.DataServices/Implementation/UserDataService.cs
Normal file
37
SeaHaven.DataServices/Implementation/UserDataService.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.DataServices.Implementation
|
||||
{
|
||||
public class UserDataService : IUserDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public UserDataService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<ApplicationUser?> GetByIdAsync(string id)
|
||||
{
|
||||
return await _context.Users.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ApplicationUser>> GetAllAsync()
|
||||
{
|
||||
return await _context.Users.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<ApplicationUser?> GetByEmailAsync(string email)
|
||||
{
|
||||
return await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Email == email);
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(string id)
|
||||
{
|
||||
return await _context.Users.AnyAsync(u => u.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
106
SeaHaven.DataServices/Implementation/VendorDataService.cs
Normal file
106
SeaHaven.DataServices/Implementation/VendorDataService.cs
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.DataServices.Implementation
|
||||
{
|
||||
public class VendorDataService : IVendorDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public VendorDataService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<Vendor?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.Vendors.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<Vendor?> GetByIdWithDetailsAsync(int id)
|
||||
{
|
||||
return await _context.Vendors
|
||||
.FirstOrDefaultAsync(v => v.Id == id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Vendor>> GetAllAsync()
|
||||
{
|
||||
return await _context.Vendors.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<Vendor> Items, int TotalCount)> GetPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null)
|
||||
{
|
||||
search ??= "";
|
||||
|
||||
var query = _context.Vendors.Where(v => v.IsActive);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var s = search.ToLower();
|
||||
query = query.Where(v =>
|
||||
(v.CompanyName ?? "").ToLower().Contains(s) ||
|
||||
(v.ContactName ?? "").ToLower().Contains(s) ||
|
||||
(v.Email ?? "").ToLower().Contains(s) ||
|
||||
(v.TradeSpecialties ?? "").ToLower().Contains(s));
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderByDescending(v => v.Id)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return (items, totalCount);
|
||||
}
|
||||
|
||||
public async Task<Vendor> AddAsync(Vendor vendor)
|
||||
{
|
||||
await _context.Vendors.AddAsync(vendor);
|
||||
await _context.SaveChangesAsync();
|
||||
return vendor;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Vendor vendor)
|
||||
{
|
||||
_context.Vendors.Update(vendor);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity != null)
|
||||
{
|
||||
_context.Vendors.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(int id)
|
||||
{
|
||||
return await _context.Vendors.AnyAsync(v => v.Id == id);
|
||||
}
|
||||
|
||||
public async Task<VendorAccessToken?> GetActiveTokenAsync(int vendorId)
|
||||
{
|
||||
return await _context.VendorAccessTokens
|
||||
.FirstOrDefaultAsync(t => t.VendorId == vendorId);
|
||||
}
|
||||
|
||||
public async Task<VendorAccessToken?> GetTokenByStringAsync(string token)
|
||||
{
|
||||
return await _context.VendorAccessTokens
|
||||
.FirstOrDefaultAsync(t => t.Token == token);
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync()
|
||||
{
|
||||
return await _context.Vendors.CountAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
367
SeaHaven.DataServices/Implementation/WorkOrderDataService.cs
Normal file
367
SeaHaven.DataServices/Implementation/WorkOrderDataService.cs
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
|
||||
namespace SeaHaven.DataServices.Implementation
|
||||
{
|
||||
public class WorkOrderDataService : IWorkOrderDataService
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public WorkOrderDataService(ApplicationDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<WorkOrder?> GetByIdAsync(int id)
|
||||
{
|
||||
return await _context.workOrders.FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<WorkOrder?> GetByIdWithDetailsAsync(int id)
|
||||
{
|
||||
return await _context.workOrders
|
||||
.Include(w => w.Locations)
|
||||
.Include(w => w.AssignToUser)
|
||||
.FirstOrDefaultAsync(w => w.Id == id);
|
||||
}
|
||||
|
||||
public async Task<WorkOrder?> GetByInternalNumberAsync(string internalNumber)
|
||||
{
|
||||
return await _context.workOrders
|
||||
.FirstOrDefaultAsync(w => w.InternalWONumber == internalNumber);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<WorkOrder>> GetAllAsync()
|
||||
{
|
||||
return await _context.workOrders.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<WorkOrder>> GetByLocationIdAsync(int locationId)
|
||||
{
|
||||
return await _context.workOrders
|
||||
.Where(w => w.LocationId == locationId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<WorkOrder>> GetByAssigneeAsync(string assignee)
|
||||
{
|
||||
return await _context.workOrders
|
||||
.Where(w => w.AssignTo == assignee)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<WorkOrder>> GetByStatusAsync(string status)
|
||||
{
|
||||
return await _context.workOrders
|
||||
.Where(w => w.Status == status)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<WorkOrder>> GetByPriorityAsync(string priority)
|
||||
{
|
||||
return await _context.workOrders
|
||||
.Where(w => w.Priority == priority)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<WorkOrder> Items, int TotalCount)> GetPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null,
|
||||
string? status = null,
|
||||
string? priority = null)
|
||||
{
|
||||
var query = _context.workOrders.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
query = query.Where(w =>
|
||||
w.WorkerOrderNumber!.Contains(search) ||
|
||||
w.WorkerOrderTitle!.Contains(search));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(status))
|
||||
{
|
||||
query = query.Where(w => w.Status == status);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(priority))
|
||||
{
|
||||
query = query.Where(w => w.Priority == priority);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderByDescending(w => w.CreatedDate)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return (items, totalCount);
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<object> Items, int TotalCount)> GetWorkOrderListPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null,
|
||||
string? status = null,
|
||||
string? assignee = null,
|
||||
int? locationId = null,
|
||||
string? sortBy = "lastUpdated",
|
||||
string? sortDir = "desc")
|
||||
{
|
||||
search ??= "";
|
||||
|
||||
var query = _context.workOrders
|
||||
.Include(w => w.Locations)
|
||||
.Include(w => w.AssignToUser)
|
||||
.Where(w => w.istemplate != true);
|
||||
|
||||
if (locationId.HasValue)
|
||||
query = query.Where(w => w.LocationId == locationId.Value);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(status))
|
||||
query = query.Where(w => w.Status == status);
|
||||
|
||||
if (assignee == "__unassigned")
|
||||
query = query.Where(w => w.AssignTo == null || w.AssignTo == "");
|
||||
else if (!string.IsNullOrWhiteSpace(assignee))
|
||||
query = query.Where(w => w.AssignToUser != null && (w.AssignToUser.FirstName + " " + w.AssignToUser.LastName).Contains(assignee));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var s = search.ToLower();
|
||||
query = query.Where(w =>
|
||||
(w.InternalWONumber ?? "").ToLower().Contains(s) ||
|
||||
(w.WorkerOrderNumber ?? "").ToLower().Contains(s) ||
|
||||
(w.WorkerOrderTitle ?? "").ToLower().Contains(s) ||
|
||||
(w.Locations.Title ?? "").ToLower().Contains(s) ||
|
||||
(w.Locations.Name ?? "").ToLower().Contains(s)
|
||||
);
|
||||
}
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var isAsc = sortDir?.ToLower() == "asc";
|
||||
IOrderedQueryable<WorkOrder> ordered = (sortBy?.ToLower()) switch
|
||||
{
|
||||
"number" => isAsc ? query.OrderBy(w => w.InternalWONumber) : query.OrderByDescending(w => w.InternalWONumber),
|
||||
"title" => isAsc ? query.OrderBy(w => w.WorkerOrderTitle) : query.OrderByDescending(w => w.WorkerOrderTitle),
|
||||
"location" => isAsc ? query.OrderBy(w => w.Locations != null ? w.Locations.Name : "") : query.OrderByDescending(w => w.Locations != null ? w.Locations.Name : ""),
|
||||
"priority" => isAsc ? query.OrderBy(w => w.Priority) : query.OrderByDescending(w => w.Priority),
|
||||
"status" => isAsc ? query.OrderBy(w => w.Status) : query.OrderByDescending(w => w.Status),
|
||||
"createddate" => isAsc ? query.OrderBy(w => w.CreatedDate) : query.OrderByDescending(w => w.CreatedDate),
|
||||
"assignedto" => isAsc ? query.OrderBy(w => w.AssignToUser != null ? w.AssignToUser.FirstName : "") : query.OrderByDescending(w => w.AssignToUser != null ? w.AssignToUser.FirstName : ""),
|
||||
"lastupdated" => isAsc ? query.OrderBy(w => w.LastModificationTime) : query.OrderByDescending(w => w.LastModificationTime),
|
||||
_ => query.OrderByDescending(w => w.Id)
|
||||
};
|
||||
|
||||
var data = await ordered
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(w => new
|
||||
{
|
||||
id = w.Id,
|
||||
internalWONumber = w.InternalWONumber,
|
||||
workOrderNumber = w.WorkerOrderNumber,
|
||||
number = w.InternalWONumber ?? w.WorkerOrderNumber,
|
||||
title = w.WorkerOrderTitle,
|
||||
location = w.Locations != null ? w.Locations.Name : "",
|
||||
locationName = w.Locations != null ? w.Locations.Name : "",
|
||||
priority = w.Priority,
|
||||
status = w.Status,
|
||||
createdDate = w.CreatedDate,
|
||||
dueDate = w.DueDate,
|
||||
assignedTo = w.AssignToUser != null ? w.AssignToUser.FirstName + " " + w.AssignToUser.LastName : "",
|
||||
employeeName = w.AssignToUser != null ? w.AssignToUser.FirstName + " " + w.AssignToUser.LastName : "",
|
||||
lastUpdated = new[] {
|
||||
w.LastModificationTime,
|
||||
w.Comments.Max(c => (DateTime?)c.CreatedDate),
|
||||
_context.WorkOrderAuditLogs.Where(a => a.WorkOrderId == w.Id).Max(a => (DateTime?)a.CreatedAt)
|
||||
}.Where(d => d.HasValue).Max()
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
return (data, totalCount);
|
||||
}
|
||||
|
||||
public async Task<WorkOrder> AddAsync(WorkOrder workOrder)
|
||||
{
|
||||
workOrder.CreatedDate = DateTime.Now;
|
||||
await _context.workOrders.AddAsync(workOrder);
|
||||
await _context.SaveChangesAsync();
|
||||
return workOrder;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(WorkOrder workOrder)
|
||||
{
|
||||
workOrder.LastModificationTime = DateTime.Now;
|
||||
_context.workOrders.Update(workOrder);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(int id)
|
||||
{
|
||||
var entity = await GetByIdAsync(id);
|
||||
if (entity != null)
|
||||
{
|
||||
_context.workOrders.Remove(entity);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<WorkOrderContacts>> GetWorkOrderContactsAsync(int workOrderId)
|
||||
{
|
||||
return await _context.WorkOrderContacts
|
||||
.Where(wc => wc.WorkorderId == workOrderId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<WorkOrderCategories>> GetWorkOrderCategoriesAsync(int workOrderId)
|
||||
{
|
||||
return await _context.workOrderCategories
|
||||
.Where(wc => wc.WorkorderId == workOrderId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<WorkOrderAttachments>> GetWorkOrderAttachmentsAsync(int workOrderId)
|
||||
{
|
||||
return await _context.workOrderAttachments
|
||||
.Where(wa => wa.WorkorderId == workOrderId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Comments>> GetWorkOrderCommentsAsync(int workOrderId)
|
||||
{
|
||||
return await _context.Comments
|
||||
.Where(c => c.WorkerOrderId == workOrderId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<WorkOrderAuditLog>> GetWorkOrderAuditLogsAsync(int workOrderId)
|
||||
{
|
||||
return await _context.WorkOrderAuditLogs
|
||||
.Where(a => a.WorkOrderId == workOrderId)
|
||||
.OrderByDescending(a => a.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task AddWorkOrderContactsAsync(int workOrderId, IEnumerable<int> contactIds)
|
||||
{
|
||||
var contacts = contactIds.Select(cid => new WorkOrderContacts
|
||||
{
|
||||
WorkorderId = workOrderId,
|
||||
ContactId = cid
|
||||
});
|
||||
|
||||
await _context.WorkOrderContacts.AddRangeAsync(contacts);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task AddWorkOrderCategoriesAsync(int workOrderId, IEnumerable<int> categoryIds)
|
||||
{
|
||||
var categories = categoryIds.Select(catId => new WorkOrderCategories
|
||||
{
|
||||
WorkorderId = workOrderId,
|
||||
CategoryId = catId
|
||||
});
|
||||
|
||||
await _context.workOrderCategories.AddRangeAsync(categories);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task AddWorkOrderAttachmentAsync(WorkOrderAttachments attachment)
|
||||
{
|
||||
await _context.workOrderAttachments.AddAsync(attachment);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task AddAuditLogAsync(WorkOrderAuditLog auditLog)
|
||||
{
|
||||
await _context.WorkOrderAuditLogs.AddAsync(auditLog);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task DeleteWorkOrderContactsAsync(int workOrderId)
|
||||
{
|
||||
var contacts = await _context.WorkOrderContacts
|
||||
.Where(wc => wc.WorkorderId == workOrderId)
|
||||
.ToListAsync();
|
||||
|
||||
if (contacts.Any())
|
||||
{
|
||||
_context.WorkOrderContacts.RemoveRange(contacts);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteWorkOrderCategoriesAsync(int workOrderId)
|
||||
{
|
||||
var categories = await _context.workOrderCategories
|
||||
.Where(wc => wc.WorkorderId == workOrderId)
|
||||
.ToListAsync();
|
||||
|
||||
if (categories.Any())
|
||||
{
|
||||
_context.workOrderCategories.RemoveRange(categories);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteWorkOrderAttachmentsAsync(int workOrderId)
|
||||
{
|
||||
var attachments = await _context.workOrderAttachments
|
||||
.Where(wa => wa.WorkorderId == workOrderId)
|
||||
.ToListAsync();
|
||||
|
||||
if (attachments.Any())
|
||||
{
|
||||
_context.workOrderAttachments.RemoveRange(attachments);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(int id)
|
||||
{
|
||||
return await _context.workOrders.AnyAsync(w => w.Id == id);
|
||||
}
|
||||
|
||||
public async Task<bool> InternalNumberExistsAsync(string internalNumber, int? excludeId = null)
|
||||
{
|
||||
return await _context.workOrders.AnyAsync(w =>
|
||||
w.InternalWONumber == internalNumber &&
|
||||
(excludeId == null || w.Id != excludeId));
|
||||
}
|
||||
|
||||
public async Task<string> GenerateNextInternalWONumberAsync()
|
||||
{
|
||||
return await Task.FromResult($"WO-{DateTime.Now:yyyyMMdd}-{new Random().Next(1000, 9999)}");
|
||||
}
|
||||
|
||||
public async Task<int> CountAsync()
|
||||
{
|
||||
return await _context.workOrders.CountAsync();
|
||||
}
|
||||
|
||||
public async Task<int> CountByStatusAsync(string status)
|
||||
{
|
||||
return await _context.workOrders.CountAsync(w => w.Status == status);
|
||||
}
|
||||
|
||||
public async Task<int> CountByLocationAsync(int locationId)
|
||||
{
|
||||
return await _context.workOrders.CountAsync(w => w.LocationId == locationId);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<WorkOrder>> GetAllWithDetailsAsync()
|
||||
{
|
||||
return await _context.workOrders
|
||||
.Where(w => w.istemplate != true)
|
||||
.Include(w => w.Locations)
|
||||
.Include(w => w.AssignToUser)
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
SeaHaven.DataServices/Interfaces/IAccountDataService.cs
Normal file
17
SeaHaven.DataServices/Interfaces/IAccountDataService.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
public interface IAccountDataService
|
||||
{
|
||||
Task<Accounts?> GetByIdAsync(int id);
|
||||
Task<Accounts?> GetByIdWithDetailsAsync(int id);
|
||||
Task<IEnumerable<Accounts>> GetAllAsync();
|
||||
Task<(IEnumerable<Accounts> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
|
||||
Task<Accounts> AddAsync(Accounts account);
|
||||
Task UpdateAsync(Accounts account);
|
||||
Task DeleteAsync(int id);
|
||||
Task<bool> ExistsAsync(int id);
|
||||
Task<int> CountAsync();
|
||||
}
|
||||
}
|
||||
18
SeaHaven.DataServices/Interfaces/IAssetDataService.cs
Normal file
18
SeaHaven.DataServices/Interfaces/IAssetDataService.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
public interface IAssetDataService
|
||||
{
|
||||
Task<Assets?> GetByIdAsync(int id);
|
||||
Task<Assets?> GetByIdWithDetailsAsync(int id);
|
||||
Task<IEnumerable<Assets>> GetAllAsync();
|
||||
Task<IEnumerable<Assets>> GetByLocationIdAsync(int locationId);
|
||||
Task<(IEnumerable<Assets> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
|
||||
Task<Assets> AddAsync(Assets asset);
|
||||
Task UpdateAsync(Assets asset);
|
||||
Task DeleteAsync(int id);
|
||||
Task<bool> ExistsAsync(int id);
|
||||
Task<int> CountAsync();
|
||||
}
|
||||
}
|
||||
14
SeaHaven.DataServices/Interfaces/ICategoryDataService.cs
Normal file
14
SeaHaven.DataServices/Interfaces/ICategoryDataService.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
public interface ICategoryDataService
|
||||
{
|
||||
Task<Category?> GetByIdAsync(int id);
|
||||
Task<IEnumerable<Category>> GetAllAsync();
|
||||
Task<Category> AddAsync(Category category);
|
||||
Task UpdateAsync(Category category);
|
||||
Task DeleteAsync(int id);
|
||||
Task<bool> ExistsAsync(int id);
|
||||
}
|
||||
}
|
||||
16
SeaHaven.DataServices/Interfaces/ICommentDataService.cs
Normal file
16
SeaHaven.DataServices/Interfaces/ICommentDataService.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
public interface ICommentDataService
|
||||
{
|
||||
Task<Comments?> GetByIdAsync(int id);
|
||||
Task<IEnumerable<Comments>> GetAllAsync();
|
||||
Task<IEnumerable<Comments>> GetByWorkOrderIdAsync(int workOrderId);
|
||||
Task<IEnumerable<Comments>> GetByDispatchIdAsync(int dispatchId);
|
||||
Task<Comments> AddAsync(Comments comment);
|
||||
Task UpdateAsync(Comments comment);
|
||||
Task DeleteAsync(int id);
|
||||
Task<bool> ExistsAsync(int id);
|
||||
}
|
||||
}
|
||||
42
SeaHaven.DataServices/Interfaces/IContactDataService.cs
Normal file
42
SeaHaven.DataServices/Interfaces/IContactDataService.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Data service interface for Contact entity
|
||||
/// Handles all data access operations for Contacts
|
||||
/// </summary>
|
||||
public interface IContactDataService
|
||||
{
|
||||
// Query operations
|
||||
Task<Contacts?> GetByIdAsync(int id);
|
||||
Task<Contacts?> GetByIdWithDetailsAsync(int id);
|
||||
Task<IEnumerable<Contacts>> GetAllAsync();
|
||||
Task<IEnumerable<Contacts>> GetByAccountIdAsync(int accountId);
|
||||
Task<IEnumerable<Contacts>> GetByTypeAsync(string type);
|
||||
Task<Contacts?> GetByEmailAsync(string email);
|
||||
Task<Contacts?> GetByPhoneAsync(string phone);
|
||||
|
||||
// Paged results
|
||||
Task<(IEnumerable<Contacts> Items, int TotalCount)> GetPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null,
|
||||
string? type = null);
|
||||
|
||||
// Command operations
|
||||
Task<Contacts> AddAsync(Contacts contact);
|
||||
Task UpdateAsync(Contacts contact);
|
||||
Task DeleteAsync(int id);
|
||||
|
||||
// Validation operations
|
||||
Task<bool> ExistsAsync(int id);
|
||||
Task<bool> EmailExistsAsync(string email, int? excludeId = null);
|
||||
Task<bool> PhoneExistsAsync(string phone, int? excludeId = null);
|
||||
|
||||
// Count operations
|
||||
Task<int> CountAsync();
|
||||
Task<int> CountByAccountIdAsync(int accountId);
|
||||
Task<int> CountByTypeAsync(string type);
|
||||
}
|
||||
}
|
||||
32
SeaHaven.DataServices/Interfaces/IDispatchDataService.cs
Normal file
32
SeaHaven.DataServices/Interfaces/IDispatchDataService.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
public interface IDispatchDataService
|
||||
{
|
||||
Task<Dispatch?> GetByIdAsync(int id);
|
||||
Task<Dispatch?> GetByIdWithDetailsAsync(int id);
|
||||
Task<IEnumerable<Dispatch>> GetAllAsync();
|
||||
Task<IEnumerable<Dispatch>> GetByVendorIdAsync(int vendorId);
|
||||
Task<IEnumerable<Dispatch>> GetByWorkOrderIdAsync(int workOrderId);
|
||||
Task<IEnumerable<Dispatch>> GetByWorkOrderIdsAsync(IEnumerable<int> workOrderIds);
|
||||
Task<IEnumerable<Dispatch>> GetByStatusAsync(string status);
|
||||
Task<(IEnumerable<Dispatch> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null, string? status = null);
|
||||
Task<Dispatch> AddAsync(Dispatch dispatch);
|
||||
Task UpdateAsync(Dispatch dispatch);
|
||||
Task DeleteAsync(int id);
|
||||
Task<bool> ExistsAsync(int id);
|
||||
Task<string> GenerateNextDispatchNumberAsync();
|
||||
Task<string> GenerateNextPONumberAsync();
|
||||
Task<int> CountAsync();
|
||||
Task<IEnumerable<DispatchChecklistItem>> GetChecklistItemsAsync(int dispatchId);
|
||||
Task<DispatchChecklistItem?> GetChecklistItemByIdAsync(int id);
|
||||
Task AddChecklistItemAsync(DispatchChecklistItem item);
|
||||
Task UpdateChecklistItemAsync(DispatchChecklistItem item);
|
||||
Task<IEnumerable<DispatchSignoff>> GetSignoffsAsync(int dispatchId);
|
||||
Task AddSignoffAsync(DispatchSignoff signoff);
|
||||
Task<bool> HasSignoffTypeAsync(int dispatchId, string signoffType);
|
||||
Task<IEnumerable<DispatchWorkOrder>> GetDispatchWorkOrdersAsync(int dispatchId);
|
||||
Task AddDispatchWorkOrderAsync(DispatchWorkOrder dispatchWorkOrder);
|
||||
}
|
||||
}
|
||||
17
SeaHaven.DataServices/Interfaces/IEmployeeDataService.cs
Normal file
17
SeaHaven.DataServices/Interfaces/IEmployeeDataService.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
public interface IEmployeeDataService
|
||||
{
|
||||
Task<Employee?> GetByIdAsync(int id);
|
||||
Task<Employee?> GetByIdWithDetailsAsync(int id);
|
||||
Task<IEnumerable<Employee>> GetAllAsync();
|
||||
Task<(IEnumerable<Employee> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
|
||||
Task<Employee> AddAsync(Employee employee);
|
||||
Task UpdateAsync(Employee employee);
|
||||
Task DeleteAsync(int id);
|
||||
Task<bool> ExistsAsync(int id);
|
||||
Task<int> CountAsync();
|
||||
}
|
||||
}
|
||||
19
SeaHaven.DataServices/Interfaces/IFollowUpDataService.cs
Normal file
19
SeaHaven.DataServices/Interfaces/IFollowUpDataService.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
public interface IFollowUpDataService
|
||||
{
|
||||
Task<FollowUps?> GetByIdAsync(int id);
|
||||
Task<FollowUps?> GetByIdWithDetailsAsync(int id);
|
||||
Task<IEnumerable<FollowUps>> GetAllAsync();
|
||||
Task<IEnumerable<FollowUps>> GetByWorkOrderIdAsync(int workOrderId);
|
||||
Task<IEnumerable<FollowUps>> GetByUserIdAsync(string userId);
|
||||
Task<(IEnumerable<FollowUps> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null, string? status = null);
|
||||
Task<FollowUps> AddAsync(FollowUps followUp);
|
||||
Task UpdateAsync(FollowUps followUp);
|
||||
Task DeleteAsync(int id);
|
||||
Task<bool> ExistsAsync(int id);
|
||||
Task<int> CountAsync();
|
||||
}
|
||||
}
|
||||
24
SeaHaven.DataServices/Interfaces/ILocationDataService.cs
Normal file
24
SeaHaven.DataServices/Interfaces/ILocationDataService.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
public interface ILocationDataService
|
||||
{
|
||||
Task<Locations?> GetByIdAsync(int id);
|
||||
Task<Locations?> GetByIdWithDetailsAsync(int id);
|
||||
Task<IEnumerable<Locations>> GetAllAsync();
|
||||
Task<IEnumerable<Locations>> GetByAccountIdAsync(int accountId);
|
||||
|
||||
// Paged results returning DTOs
|
||||
Task<(IEnumerable<Locations> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
|
||||
|
||||
// For addressbook endpoint - returns simplified DTO
|
||||
Task<(IEnumerable<object> Items, int TotalCount)> GetAddressbookPagedAsync(int page, int pageSize, string? search = null);
|
||||
|
||||
Task<Locations> AddAsync(Locations location);
|
||||
Task UpdateAsync(Locations location);
|
||||
Task DeleteAsync(int id);
|
||||
Task<bool> ExistsAsync(int id);
|
||||
Task<int> CountAsync();
|
||||
}
|
||||
}
|
||||
19
SeaHaven.DataServices/Interfaces/IPMScheduleDataService.cs
Normal file
19
SeaHaven.DataServices/Interfaces/IPMScheduleDataService.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
public interface IPMScheduleDataService
|
||||
{
|
||||
Task<PMSchedules?> GetByIdAsync(int id);
|
||||
Task<PMSchedules?> GetByIdWithDetailsAsync(int id);
|
||||
Task<IEnumerable<PMSchedules>> GetAllAsync();
|
||||
Task<IEnumerable<PMSchedules>> GetAllWithDetailsAsync();
|
||||
Task<IEnumerable<PMSchedules>> GetByLocationIdAsync(int locationId);
|
||||
Task<(IEnumerable<PMSchedules> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
|
||||
Task<PMSchedules> AddAsync(PMSchedules pmSchedule);
|
||||
Task UpdateAsync(PMSchedules pmSchedule);
|
||||
Task DeleteAsync(int id);
|
||||
Task<bool> ExistsAsync(int id);
|
||||
Task<int> CountAsync();
|
||||
}
|
||||
}
|
||||
16
SeaHaven.DataServices/Interfaces/IQuotesDataService.cs
Normal file
16
SeaHaven.DataServices/Interfaces/IQuotesDataService.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
public interface IQuotesDataService
|
||||
{
|
||||
Task<Quotes?> GetByIdAsync(int id);
|
||||
Task<IEnumerable<Quotes>> GetAllAsync();
|
||||
Task<IEnumerable<Quotes>> GetByWorkOrderIdAsync(int workOrderId);
|
||||
Task<Quotes> AddAsync(Quotes quote);
|
||||
Task UpdateAsync(Quotes quote);
|
||||
Task DeleteAsync(int id);
|
||||
Task<bool> ExistsAsync(int id);
|
||||
Task UnlinkFromWorkOrderAsync(int workOrderId);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
public interface ITaskListTemplateDataService
|
||||
{
|
||||
Task<TaskListTemplate?> GetByIdAsync(int id);
|
||||
Task<TaskListTemplate?> GetByIdWithItemsAsync(int id);
|
||||
Task<IEnumerable<TaskListTemplate>> GetAllAsync();
|
||||
Task<TaskListTemplate> AddAsync(TaskListTemplate template);
|
||||
Task UpdateAsync(TaskListTemplate template);
|
||||
Task DeleteAsync(int id);
|
||||
Task<bool> ExistsAsync(int id);
|
||||
}
|
||||
}
|
||||
12
SeaHaven.DataServices/Interfaces/IUserDataService.cs
Normal file
12
SeaHaven.DataServices/Interfaces/IUserDataService.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
public interface IUserDataService
|
||||
{
|
||||
Task<ApplicationUser?> GetByIdAsync(string id);
|
||||
Task<IEnumerable<ApplicationUser>> GetAllAsync();
|
||||
Task<ApplicationUser?> GetByEmailAsync(string email);
|
||||
Task<bool> ExistsAsync(string id);
|
||||
}
|
||||
}
|
||||
19
SeaHaven.DataServices/Interfaces/IVendorDataService.cs
Normal file
19
SeaHaven.DataServices/Interfaces/IVendorDataService.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
public interface IVendorDataService
|
||||
{
|
||||
Task<Vendor?> GetByIdAsync(int id);
|
||||
Task<Vendor?> GetByIdWithDetailsAsync(int id);
|
||||
Task<IEnumerable<Vendor>> GetAllAsync();
|
||||
Task<(IEnumerable<Vendor> Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null);
|
||||
Task<Vendor> AddAsync(Vendor vendor);
|
||||
Task UpdateAsync(Vendor vendor);
|
||||
Task DeleteAsync(int id);
|
||||
Task<bool> ExistsAsync(int id);
|
||||
Task<VendorAccessToken?> GetActiveTokenAsync(int vendorId);
|
||||
Task<VendorAccessToken?> GetTokenByStringAsync(string token);
|
||||
Task<int> CountAsync();
|
||||
}
|
||||
}
|
||||
72
SeaHaven.DataServices/Interfaces/IWorkOrderDataService.cs
Normal file
72
SeaHaven.DataServices/Interfaces/IWorkOrderDataService.cs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
public interface IWorkOrderDataService
|
||||
{
|
||||
// Query operations
|
||||
Task<WorkOrder?> GetByIdAsync(int id);
|
||||
Task<WorkOrder?> GetByIdWithDetailsAsync(int id);
|
||||
Task<WorkOrder?> GetByInternalNumberAsync(string internalNumber);
|
||||
Task<IEnumerable<WorkOrder>> GetAllAsync();
|
||||
Task<IEnumerable<WorkOrder>> GetByLocationIdAsync(int locationId);
|
||||
Task<IEnumerable<WorkOrder>> GetByAssigneeAsync(string assigneeId);
|
||||
Task<IEnumerable<WorkOrder>> GetByStatusAsync(string status);
|
||||
Task<IEnumerable<WorkOrder>> GetByPriorityAsync(string priority);
|
||||
|
||||
// Paged results
|
||||
Task<(IEnumerable<WorkOrder> Items, int TotalCount)> GetPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null,
|
||||
string? status = null,
|
||||
string? priority = null);
|
||||
|
||||
// Paged list with detailed information (for GetWorkOrderList endpoint)
|
||||
Task<(IEnumerable<object> Items, int TotalCount)> GetWorkOrderListPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null,
|
||||
string? status = null,
|
||||
string? assignee = null,
|
||||
int? locationId = null,
|
||||
string? sortBy = "lastUpdated",
|
||||
string? sortDir = "desc");
|
||||
|
||||
// Command operations
|
||||
Task<WorkOrder> AddAsync(WorkOrder workOrder);
|
||||
Task UpdateAsync(WorkOrder workOrder);
|
||||
Task DeleteAsync(int id);
|
||||
|
||||
// Related entities - Read
|
||||
Task<IEnumerable<WorkOrderContacts>> GetWorkOrderContactsAsync(int workOrderId);
|
||||
Task<IEnumerable<WorkOrderCategories>> GetWorkOrderCategoriesAsync(int workOrderId);
|
||||
Task<IEnumerable<WorkOrderAttachments>> GetWorkOrderAttachmentsAsync(int workOrderId);
|
||||
Task<IEnumerable<Comments>> GetWorkOrderCommentsAsync(int workOrderId);
|
||||
Task<IEnumerable<WorkOrderAuditLog>> GetWorkOrderAuditLogsAsync(int workOrderId);
|
||||
|
||||
// Related entities - Write
|
||||
Task AddWorkOrderContactsAsync(int workOrderId, IEnumerable<int> contactIds);
|
||||
Task AddWorkOrderCategoriesAsync(int workOrderId, IEnumerable<int> categoryIds);
|
||||
Task AddWorkOrderAttachmentAsync(WorkOrderAttachments attachment);
|
||||
Task AddAuditLogAsync(WorkOrderAuditLog auditLog);
|
||||
|
||||
// Related entities - Delete
|
||||
Task DeleteWorkOrderContactsAsync(int workOrderId);
|
||||
Task DeleteWorkOrderCategoriesAsync(int workOrderId);
|
||||
Task DeleteWorkOrderAttachmentsAsync(int workOrderId);
|
||||
|
||||
// Validation
|
||||
Task<bool> ExistsAsync(int id);
|
||||
Task<bool> InternalNumberExistsAsync(string internalNumber, int? excludeId = null);
|
||||
|
||||
// Internal number generation
|
||||
Task<string> GenerateNextInternalWONumberAsync();
|
||||
|
||||
// Statistics
|
||||
Task<int> CountAsync();
|
||||
Task<int> CountByStatusAsync(string status);
|
||||
Task<int> CountByLocationAsync(int locationId);
|
||||
Task<IEnumerable<WorkOrder>> GetAllWithDetailsAsync();
|
||||
}
|
||||
}
|
||||
17
SeaHaven.DataServices/SeaHaven.DataServices.csproj
Normal file
17
SeaHaven.DataServices/SeaHaven.DataServices.csproj
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Data.SeaHavenIndustries\Data.SeaHavenIndustries.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
34
SeaHaven.Services.Tests/PagedResultTests.cs
Normal file
34
SeaHaven.Services.Tests/PagedResultTests.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using FluentAssertions;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using Xunit;
|
||||
|
||||
namespace SeaHaven.Services.Tests;
|
||||
|
||||
public class PagedResultTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0, 10, 0)]
|
||||
[InlineData(10, 10, 1)]
|
||||
[InlineData(25, 10, 3)]
|
||||
[InlineData(100, 25, 4)]
|
||||
public void TotalPages_IsCeilingOfCountOverPageSize(int totalCount, int pageSize, int expectedPages)
|
||||
{
|
||||
var result = new PagedResult<string> { TotalCount = totalCount, PageSize = pageSize, Page = 1 };
|
||||
|
||||
result.TotalPages.Should().Be(expectedPages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasPrevious_IsFalseOnFirstPage_TrueAfterward()
|
||||
{
|
||||
new PagedResult<string> { Page = 1, PageSize = 10, TotalCount = 30 }.HasPrevious.Should().BeFalse();
|
||||
new PagedResult<string> { Page = 2, PageSize = 10, TotalCount = 30 }.HasPrevious.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasNext_IsTrueBeforeLastPage_FalseOnLastPage()
|
||||
{
|
||||
new PagedResult<string> { Page = 2, PageSize = 10, TotalCount = 30 }.HasNext.Should().BeTrue();
|
||||
new PagedResult<string> { Page = 3, PageSize = 10, TotalCount = 30 }.HasNext.Should().BeFalse();
|
||||
}
|
||||
}
|
||||
23
SeaHaven.Services.Tests/SeaHaven.Services.Tests.csproj
Normal file
23
SeaHaven.Services.Tests/SeaHaven.Services.Tests.csproj
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.1" />
|
||||
<PackageReference Include="FluentValidation" Version="12.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SeaHaven.Services\SeaHaven.Services.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
104
SeaHaven.Services.Tests/ValidationTests.cs
Normal file
104
SeaHaven.Services.Tests/ValidationTests.cs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
using FluentAssertions;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.Services.Validation;
|
||||
using Xunit;
|
||||
|
||||
namespace SeaHaven.Services.Tests;
|
||||
|
||||
public class CreateAssetValidationTests
|
||||
{
|
||||
private readonly CreateAssetValidation _validator = new();
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenNameEmpty_Fails()
|
||||
{
|
||||
var result = _validator.Validate(new CreateAssetDTO { Name = "" });
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.PropertyName == nameof(CreateAssetDTO.Name));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenNameTooLong_Fails()
|
||||
{
|
||||
var result = _validator.Validate(new CreateAssetDTO { Name = new string('a', 201) });
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.PropertyName == nameof(CreateAssetDTO.Name));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenLocationIdZero_Fails()
|
||||
{
|
||||
var result = _validator.Validate(new CreateAssetDTO { Name = "Pump", LocationId = 0 });
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.PropertyName == nameof(CreateAssetDTO.LocationId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenValid_Passes()
|
||||
{
|
||||
var result = _validator.Validate(new CreateAssetDTO { Name = "Pump", LocationId = 5 });
|
||||
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateContactValidationTests
|
||||
{
|
||||
private readonly CreateContactValidation _validator = new();
|
||||
|
||||
private static CreateContactDTO Valid() => new()
|
||||
{
|
||||
FirstName = "Ada",
|
||||
LastName = "Lovelace",
|
||||
Email = "ada@example.com"
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenNamesMissing_Fails()
|
||||
{
|
||||
var dto = Valid();
|
||||
dto.FirstName = "";
|
||||
dto.LastName = "";
|
||||
|
||||
var result = _validator.Validate(dto);
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.PropertyName == nameof(CreateContactDTO.FirstName));
|
||||
result.Errors.Should().Contain(e => e.PropertyName == nameof(CreateContactDTO.LastName));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenEmailMalformed_Fails()
|
||||
{
|
||||
var dto = Valid();
|
||||
dto.Email = "not-an-email";
|
||||
|
||||
var result = _validator.Validate(dto);
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.PropertyName == nameof(CreateContactDTO.Email));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenPhoneTooLong_Fails()
|
||||
{
|
||||
var dto = Valid();
|
||||
dto.Phone = new string('1', 21);
|
||||
|
||||
var result = _validator.Validate(dto);
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.PropertyName == nameof(CreateContactDTO.Phone));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenValid_Passes()
|
||||
{
|
||||
var result = _validator.Validate(Valid());
|
||||
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
74
SeaHaven.Services.Tests/WorkOrderMappingTests.cs
Normal file
74
SeaHaven.Services.Tests/WorkOrderMappingTests.cs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Data.SeaHavenIndustries.Enums;
|
||||
using FluentAssertions;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using Xunit;
|
||||
|
||||
namespace SeaHaven.Services.Tests;
|
||||
|
||||
public class WorkOrderMappingTests
|
||||
{
|
||||
[Fact]
|
||||
public void MapToModel_WithValidEnumStrings_ParsesStatusAndPriority()
|
||||
{
|
||||
var entity = new WorkOrder
|
||||
{
|
||||
Id = 7,
|
||||
WorkerOrderTitle = "Fix pump",
|
||||
Status = "InProgress",
|
||||
Priority = "High"
|
||||
};
|
||||
|
||||
var dto = WorkOrderDTO.MapToModel(entity);
|
||||
|
||||
dto.Id.Should().Be(7);
|
||||
dto.WorkerOrderTitle.Should().Be("Fix pump");
|
||||
dto.Status.Should().Be(WorkOrderStatus.InProgress);
|
||||
dto.Priority.Should().Be(WorkOrderPriority.High);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapToModel_WithUnparseableEnumStrings_FallsBackToDefaults()
|
||||
{
|
||||
var entity = new WorkOrder
|
||||
{
|
||||
WorkerOrderTitle = "Garbage",
|
||||
Status = "not-a-status",
|
||||
Priority = "not-a-priority"
|
||||
};
|
||||
|
||||
var dto = WorkOrderDTO.MapToModel(entity);
|
||||
|
||||
dto.Status.Should().Be(WorkOrderStatus.Open);
|
||||
dto.Priority.Should().Be(WorkOrderPriority.Medium);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapToEntity_SerializesEnumsToStrings()
|
||||
{
|
||||
var dto = new WorkOrderDTO
|
||||
{
|
||||
Id = 3,
|
||||
WorkerOrderTitle = "Inspect",
|
||||
Status = WorkOrderStatus.Completed,
|
||||
Priority = WorkOrderPriority.Critical
|
||||
};
|
||||
|
||||
var entity = WorkOrderDTO.MapToEntity(dto);
|
||||
|
||||
entity.Status.Should().Be("Completed");
|
||||
entity.Priority.Should().Be("Critical");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateMapToEntity_WhenStatusAndPriorityNull_AppliesDefaults()
|
||||
{
|
||||
var dto = new CreateWorkOrderDTO { WorkerOrderTitle = "New" };
|
||||
|
||||
var entity = CreateWorkOrderDTO.MapToEntity(dto);
|
||||
|
||||
entity.Status.Should().Be("Open");
|
||||
entity.Priority.Should().Be("Medium");
|
||||
entity.CreatedDate.Should().NotBeNull();
|
||||
}
|
||||
}
|
||||
30
SeaHaven.Services/DTOs/AccountDTOs.cs
Normal file
30
SeaHaven.Services/DTOs/AccountDTOs.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
namespace SeaHaven.Services.DTOs
|
||||
{
|
||||
public class AccountDTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Type { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Website { get; set; }
|
||||
public DateTime? CreatedDate { get; set; }
|
||||
public string? CreatedBy { get; set; }
|
||||
}
|
||||
|
||||
public class CreateAccountDTO
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Website { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateAccountDTO
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Website { get; set; }
|
||||
}
|
||||
}
|
||||
23
SeaHaven.Services/DTOs/AssetDTOs.cs
Normal file
23
SeaHaven.Services/DTOs/AssetDTOs.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
namespace SeaHaven.Services.DTOs
|
||||
{
|
||||
public class AssetDTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
public string? LocationName { get; set; }
|
||||
public DateTime? CreatedDate { get; set; }
|
||||
}
|
||||
|
||||
public class CreateAssetDTO
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateAssetDTO
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
}
|
||||
}
|
||||
55
SeaHaven.Services/DTOs/ContactDTOs.cs
Normal file
55
SeaHaven.Services/DTOs/ContactDTOs.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
namespace SeaHaven.Services.DTOs
|
||||
{
|
||||
public class ContactDTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string? Mobile { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? Type { get; set; }
|
||||
public int? AccountId { get; set; }
|
||||
public string? AccountName { get; set; }
|
||||
public DateTime? CreatedDate { get; set; }
|
||||
public string? CreatedBy { get; set; }
|
||||
public DateTime? ModifiedDate { get; set; }
|
||||
public string? ModifiedBy { get; set; }
|
||||
}
|
||||
|
||||
public class CreateContactDTO
|
||||
{
|
||||
public required string FirstName { get; set; }
|
||||
public required string LastName { get; set; }
|
||||
public required string Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string? Mobile { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? Type { get; set; }
|
||||
public int? AccountId { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateContactDTO
|
||||
{
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string? Mobile { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? Type { get; set; }
|
||||
public int? AccountId { get; set; }
|
||||
}
|
||||
|
||||
public class PagedResult<T>
|
||||
{
|
||||
public IEnumerable<T> Items { get; set; } = new List<T>();
|
||||
public int TotalCount { get; set; }
|
||||
public int Page { get; set; }
|
||||
public int PageSize { get; set; }
|
||||
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
|
||||
public bool HasPrevious => Page > 1;
|
||||
public bool HasNext => Page < TotalPages;
|
||||
}
|
||||
}
|
||||
30
SeaHaven.Services/DTOs/DispatchDTOs.cs
Normal file
30
SeaHaven.Services/DTOs/DispatchDTOs.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
namespace SeaHaven.Services.DTOs
|
||||
{
|
||||
public class DispatchDTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? DispatchNumber { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public int? VendorId { get; set; }
|
||||
public string? VendorName { get; set; }
|
||||
public DateTime? ScheduledDate { get; set; }
|
||||
public DateTime? CompletedDate { get; set; }
|
||||
public DateTime? CreatedDate { get; set; }
|
||||
public string? CreatedBy { get; set; }
|
||||
}
|
||||
|
||||
public class CreateDispatchDTO
|
||||
{
|
||||
public required int VendorId { get; set; }
|
||||
public DateTime? ScheduledDate { get; set; }
|
||||
public string? Status { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateDispatchDTO
|
||||
{
|
||||
public int? VendorId { get; set; }
|
||||
public DateTime? ScheduledDate { get; set; }
|
||||
public DateTime? CompletedDate { get; set; }
|
||||
public string? Status { get; set; }
|
||||
}
|
||||
}
|
||||
40
SeaHaven.Services/DTOs/EmployeeDTOs.cs
Normal file
40
SeaHaven.Services/DTOs/EmployeeDTOs.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
namespace SeaHaven.Services.DTOs
|
||||
{
|
||||
public class EmployeeDTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public int? DepartmentId { get; set; }
|
||||
public string? DepartmentName { get; set; }
|
||||
public int? JobTitleId { get; set; }
|
||||
public string? JobTitleName { get; set; }
|
||||
public int? RegionId { get; set; }
|
||||
public string? RegionName { get; set; }
|
||||
public DateTime? CreatedDate { get; set; }
|
||||
}
|
||||
|
||||
public class CreateEmployeeDTO
|
||||
{
|
||||
public required string FirstName { get; set; }
|
||||
public required string LastName { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public int? DepartmentId { get; set; }
|
||||
public int? JobTitleId { get; set; }
|
||||
public int? RegionId { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateEmployeeDTO
|
||||
{
|
||||
public string? FirstName { get; set; }
|
||||
public string? LastName { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public int? DepartmentId { get; set; }
|
||||
public int? JobTitleId { get; set; }
|
||||
public int? RegionId { get; set; }
|
||||
}
|
||||
}
|
||||
43
SeaHaven.Services/DTOs/FollowUpDTOs.cs
Normal file
43
SeaHaven.Services/DTOs/FollowUpDTOs.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
namespace SeaHaven.Services.DTOs
|
||||
{
|
||||
public class FollowUpDTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? Reason { get; set; }
|
||||
public DateTime ScheduleDate { get; set; }
|
||||
public TimeSpan ScheduleStartTime { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public int? EmployeeId { get; set; }
|
||||
public string? EmployeeName { get; set; }
|
||||
public int? WorkOrderId { get; set; }
|
||||
public int? AccountId { get; set; }
|
||||
public string? AccountName { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
public string? LocationName { get; set; }
|
||||
public DateTime? CreatedDate { get; set; }
|
||||
}
|
||||
|
||||
public class CreateFollowUpDTO
|
||||
{
|
||||
public required string Reason { get; set; }
|
||||
public DateTime ScheduleDate { get; set; }
|
||||
public TimeSpan ScheduleStartTime { get; set; }
|
||||
public required string Status { get; set; }
|
||||
public int? EmployeeId { get; set; }
|
||||
public int? WorkOrderId { get; set; }
|
||||
public int? AccountId { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateFollowUpDTO
|
||||
{
|
||||
public string? Reason { get; set; }
|
||||
public DateTime? ScheduleDate { get; set; }
|
||||
public TimeSpan? ScheduleStartTime { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public int? EmployeeId { get; set; }
|
||||
public int? WorkOrderId { get; set; }
|
||||
public int? AccountId { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
}
|
||||
}
|
||||
39
SeaHaven.Services/DTOs/LocationDTOs.cs
Normal file
39
SeaHaven.Services/DTOs/LocationDTOs.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
namespace SeaHaven.Services.DTOs
|
||||
{
|
||||
public class LocationDTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? Address1 { get; set; }
|
||||
public string? Address2 { get; set; }
|
||||
public string? City { get; set; }
|
||||
public string? State { get; set; }
|
||||
public string? Zip { get; set; }
|
||||
public string? PhoneNumber { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public DateTime? CreatedDate { get; set; }
|
||||
public string? CreatedBy { get; set; }
|
||||
}
|
||||
|
||||
public class CreateLocationDTO
|
||||
{
|
||||
public required string LocationName { get; set; }
|
||||
public string? Address { get; set; }
|
||||
public string? City { get; set; }
|
||||
public string? State { get; set; }
|
||||
public string? Zipcode { get; set; }
|
||||
public int? AccountId { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateLocationDTO
|
||||
{
|
||||
public string? LocationName { get; set; }
|
||||
public string? Address { get; set; }
|
||||
public string? City { get; set; }
|
||||
public string? State { get; set; }
|
||||
public string? Zipcode { get; set; }
|
||||
public int? AccountId { get; set; }
|
||||
}
|
||||
}
|
||||
36
SeaHaven.Services/DTOs/PMScheduleDTOs.cs
Normal file
36
SeaHaven.Services/DTOs/PMScheduleDTOs.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
namespace SeaHaven.Services.DTOs
|
||||
{
|
||||
public class PMScheduleDTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Frequency { get; set; }
|
||||
public DateTime StartDate { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public int? AssetId { get; set; }
|
||||
public string? AssetName { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
public string? LocationName { get; set; }
|
||||
public DateTime? CreatedDate { get; set; }
|
||||
}
|
||||
|
||||
public class CreatePMScheduleDTO
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public required string Frequency { get; set; }
|
||||
public DateTime StartDate { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public int? AssetId { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
}
|
||||
|
||||
public class UpdatePMScheduleDTO
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? Frequency { get; set; }
|
||||
public DateTime? StartDate { get; set; }
|
||||
public bool? IsActive { get; set; }
|
||||
public int? AssetId { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
}
|
||||
}
|
||||
36
SeaHaven.Services/DTOs/VendorDTOs.cs
Normal file
36
SeaHaven.Services/DTOs/VendorDTOs.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
namespace SeaHaven.Services.DTOs
|
||||
{
|
||||
public class VendorDTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? Name { 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? Zipcode { get; set; }
|
||||
}
|
||||
|
||||
public class CreateVendorDTO
|
||||
{
|
||||
public required string Name { 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? Zipcode { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateVendorDTO
|
||||
{
|
||||
public string? Name { 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? Zipcode { get; set; }
|
||||
}
|
||||
}
|
||||
129
SeaHaven.Services/DTOs/WorkOrderDTOs.cs
Normal file
129
SeaHaven.Services/DTOs/WorkOrderDTOs.cs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Data.SeaHavenIndustries.Enums;
|
||||
|
||||
namespace SeaHaven.Services.DTOs
|
||||
{
|
||||
public class WorkOrderDTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? InternalWONumber { get; set; }
|
||||
public string? WorkerOrderTitle { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public WorkOrderStatus Status { get; set; }
|
||||
public WorkOrderPriority Priority { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
public string? LocationName { get; set; }
|
||||
public string? AssignTo { get; set; }
|
||||
public DateTime? DueDate { get; set; }
|
||||
public DateTime? CompletedDate { get; set; }
|
||||
public DateTime? CreatedDate { get; set; }
|
||||
public string? CreatedBy { get; set; }
|
||||
public DateTime? ModifiedDate { get; set; }
|
||||
public string? ModifiedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maps entity to DTO (Entity -> DTO)
|
||||
/// </summary>
|
||||
public static WorkOrderDTO MapToModel(WorkOrder entity)
|
||||
{
|
||||
if (entity == null) return null;
|
||||
|
||||
return new WorkOrderDTO
|
||||
{
|
||||
Id = entity.Id,
|
||||
InternalWONumber = entity.InternalWONumber,
|
||||
WorkerOrderTitle = entity.WorkerOrderTitle,
|
||||
Description = entity.Description,
|
||||
Status = Enum.TryParse<WorkOrderStatus>(entity.Status, out var status) ? status : WorkOrderStatus.Open,
|
||||
Priority = Enum.TryParse<WorkOrderPriority>(entity.Priority, out var priority) ? priority : WorkOrderPriority.Medium,
|
||||
LocationId = entity.LocationId,
|
||||
LocationName = null,
|
||||
AssignTo = entity.AssignTo,
|
||||
DueDate = entity.DueDate,
|
||||
CompletedDate = entity.CompletedDate,
|
||||
CreatedDate = entity.CreatedDate,
|
||||
CreatedBy = entity.createdby,
|
||||
ModifiedDate = entity.LastModificationTime,
|
||||
ModifiedBy = entity.LastModifierUserId?.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps DTO to entity (DTO -> Entity)
|
||||
/// </summary>
|
||||
public static WorkOrder MapToEntity(WorkOrderDTO dto)
|
||||
{
|
||||
if (dto == null) return null;
|
||||
|
||||
return new WorkOrder
|
||||
{
|
||||
Id = dto.Id,
|
||||
InternalWONumber = dto.InternalWONumber,
|
||||
WorkerOrderTitle = dto.WorkerOrderTitle,
|
||||
Description = dto.Description,
|
||||
Status = dto.Status.ToString(),
|
||||
Priority = dto.Priority.ToString(),
|
||||
LocationId = dto.LocationId,
|
||||
AssignTo = dto.AssignTo,
|
||||
DueDate = dto.DueDate,
|
||||
CompletedDate = dto.CompletedDate
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateWorkOrderDTO
|
||||
{
|
||||
public required string WorkerOrderTitle { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public WorkOrderStatus? Status { get; set; }
|
||||
public WorkOrderPriority? Priority { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
public string? AssignTo { get; set; }
|
||||
public DateTime? DueDate { get; set; }
|
||||
|
||||
public static WorkOrder MapToEntity(CreateWorkOrderDTO dto)
|
||||
{
|
||||
if (dto == null) return null!;
|
||||
|
||||
return new WorkOrder
|
||||
{
|
||||
WorkerOrderTitle = dto.WorkerOrderTitle,
|
||||
Description = dto.Description,
|
||||
Status = (dto.Status ?? WorkOrderStatus.Open).ToString(),
|
||||
Priority = (dto.Priority ?? WorkOrderPriority.Medium).ToString(),
|
||||
LocationId = dto.LocationId,
|
||||
AssignTo = dto.AssignTo,
|
||||
DueDate = dto.DueDate,
|
||||
CreatedDate = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateWorkOrderDTO
|
||||
{
|
||||
public string? WorkerOrderTitle { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public WorkOrderStatus? Status { get; set; }
|
||||
public WorkOrderPriority? Priority { get; set; }
|
||||
public int? LocationId { get; set; }
|
||||
public string? AssignTo { get; set; }
|
||||
public DateTime? DueDate { get; set; }
|
||||
public DateTime? CompletedDate { get; set; }
|
||||
|
||||
public static void UpdateEntity(UpdateWorkOrderDTO dto, WorkOrder entity)
|
||||
{
|
||||
if (dto == null || entity == null) return;
|
||||
|
||||
if (dto.WorkerOrderTitle != null) entity.WorkerOrderTitle = dto.WorkerOrderTitle;
|
||||
if (dto.Description != null) entity.Description = dto.Description;
|
||||
if (dto.Status.HasValue) entity.Status = dto.Status.ToString()!;
|
||||
if (dto.Priority.HasValue) entity.Priority = dto.Priority.ToString()!;
|
||||
if (dto.LocationId.HasValue) entity.LocationId = dto.LocationId;
|
||||
if (dto.AssignTo != null) entity.AssignTo = dto.AssignTo;
|
||||
if (dto.DueDate.HasValue) entity.DueDate = dto.DueDate;
|
||||
if (dto.CompletedDate.HasValue) entity.CompletedDate = dto.CompletedDate;
|
||||
|
||||
entity.LastModificationTime = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
SeaHaven.Services/DependencyInjection/ServicesModule.cs
Normal file
38
SeaHaven.Services/DependencyInjection/ServicesModule.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using FluentValidation;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SeaHaven.Services.DependencyInjection
|
||||
{
|
||||
public static class ServicesModule
|
||||
{
|
||||
private static readonly List<Type> ExcludedTypes = new();
|
||||
|
||||
public static IServiceCollection AddBusinessServices(this IServiceCollection services)
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
var allClasses = assembly.GetTypes()
|
||||
.Where(t => t.IsClass &&
|
||||
!t.IsAbstract &&
|
||||
!t.IsGenericType &&
|
||||
!ExcludedTypes.Contains(t))
|
||||
.ToList();
|
||||
|
||||
foreach (var implementationType in allClasses)
|
||||
{
|
||||
var defaultInterface = implementationType.GetInterfaces()
|
||||
.FirstOrDefault(i => i.Name == $"I{implementationType.Name}");
|
||||
|
||||
if (defaultInterface != null)
|
||||
{
|
||||
services.AddScoped(defaultInterface, implementationType);
|
||||
}
|
||||
}
|
||||
|
||||
services.AddValidatorsFromAssembly(assembly);
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
145
SeaHaven.Services/Implementation/AccountService.cs
Normal file
145
SeaHaven.Services/Implementation/AccountService.cs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.Validation;
|
||||
|
||||
namespace SeaHaven.Services.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Account service implementation
|
||||
/// Contains business logic, validation, and orchestration for Account operations
|
||||
/// </summary>
|
||||
public class AccountService : IAccountService
|
||||
{
|
||||
private readonly IAccountDataService _accountDataService;
|
||||
private readonly ICreateAccountValidation _createValidator;
|
||||
private readonly IUpdateAccountValidation _updateValidator;
|
||||
|
||||
public AccountService(
|
||||
IAccountDataService accountDataService,
|
||||
ICreateAccountValidation createValidator,
|
||||
IUpdateAccountValidation updateValidator)
|
||||
{
|
||||
_accountDataService = accountDataService;
|
||||
_createValidator = createValidator;
|
||||
_updateValidator = updateValidator;
|
||||
}
|
||||
|
||||
public async Task<AccountDTO?> GetAccountByIdAsync(int id)
|
||||
{
|
||||
var account = await _accountDataService.GetByIdAsync(id);
|
||||
return account == null ? null : MapToDTO(account);
|
||||
}
|
||||
|
||||
public async Task<AccountDTO?> GetAccountByIdWithDetailsAsync(int id)
|
||||
{
|
||||
var account = await _accountDataService.GetByIdWithDetailsAsync(id);
|
||||
return account == null ? null : MapToDTO(account);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AccountDTO>> GetAllAccountsAsync()
|
||||
{
|
||||
var accounts = await _accountDataService.GetAllAsync();
|
||||
return accounts.Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<PagedResult<AccountDTO>> GetAccountsPagedAsync(int page, int pageSize, string? search = null)
|
||||
{
|
||||
var (items, totalCount) = await _accountDataService.GetPagedAsync(page, pageSize, search);
|
||||
return new PagedResult<AccountDTO>
|
||||
{
|
||||
Items = items.Select(MapToDTO).ToList(),
|
||||
TotalCount = totalCount,
|
||||
Page = page,
|
||||
PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<AccountDTO> CreateAccountAsync(CreateAccountDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _createValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var account = new Accounts
|
||||
{
|
||||
Name = dto.Name,
|
||||
Phone = dto.Phone,
|
||||
Email = dto.Email,
|
||||
Website = dto.Website,
|
||||
CreatedDate = DateTime.UtcNow,
|
||||
createdby = userId
|
||||
};
|
||||
|
||||
var created = await _accountDataService.AddAsync(account);
|
||||
return MapToDTO(created);
|
||||
}
|
||||
|
||||
public async Task<AccountDTO> UpdateAccountAsync(int id, UpdateAccountDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _updateValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var account = await _accountDataService.GetByIdAsync(id);
|
||||
if (account == null)
|
||||
throw new InvalidOperationException($"Account with ID {id} not found");
|
||||
|
||||
if (dto.Name != null) account.Name = dto.Name;
|
||||
if (dto.Phone != null) account.Phone = dto.Phone;
|
||||
if (dto.Email != null) account.Email = dto.Email;
|
||||
if (dto.Website != null) account.Website = dto.Website;
|
||||
|
||||
account.LastModificationTime = DateTime.UtcNow;
|
||||
if (int.TryParse(userId, out int userIdInt))
|
||||
account.LastModifierUserId = userIdInt;
|
||||
|
||||
await _accountDataService.UpdateAsync(account);
|
||||
return MapToDTO(account);
|
||||
}
|
||||
|
||||
public async Task DeleteAccountAsync(int id, string userId)
|
||||
{
|
||||
var account = await _accountDataService.GetByIdAsync(id);
|
||||
if (account == null)
|
||||
throw new InvalidOperationException($"Account with ID {id} not found");
|
||||
|
||||
account.IsDeleted = true;
|
||||
account.DeleterUserId = userId;
|
||||
account.DeletionTime = DateTime.UtcNow;
|
||||
|
||||
await _accountDataService.UpdateAsync(account);
|
||||
}
|
||||
|
||||
public async Task<bool> AccountExistsAsync(int id)
|
||||
{
|
||||
return await _accountDataService.ExistsAsync(id);
|
||||
}
|
||||
|
||||
public async Task<int> GetTotalAccountCountAsync()
|
||||
{
|
||||
return await _accountDataService.CountAsync();
|
||||
}
|
||||
|
||||
private AccountDTO MapToDTO(Accounts account)
|
||||
{
|
||||
return new AccountDTO
|
||||
{
|
||||
Id = account.Id,
|
||||
Name = account.Name,
|
||||
Type = account.Type,
|
||||
Phone = account.Phone,
|
||||
Email = account.Email,
|
||||
Website = account.Website,
|
||||
CreatedDate = account.CreatedDate,
|
||||
CreatedBy = account.createdby
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
144
SeaHaven.Services/Implementation/AssetService.cs
Normal file
144
SeaHaven.Services/Implementation/AssetService.cs
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.Validation;
|
||||
|
||||
namespace SeaHaven.Services.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Asset service implementation
|
||||
/// Contains business logic, validation, and orchestration for Asset operations
|
||||
/// </summary>
|
||||
public class AssetService : IAssetService
|
||||
{
|
||||
private readonly IAssetDataService _assetDataService;
|
||||
private readonly ICreateAssetValidation _createValidator;
|
||||
private readonly IUpdateAssetValidation _updateValidator;
|
||||
|
||||
public AssetService(
|
||||
IAssetDataService assetDataService,
|
||||
ICreateAssetValidation createValidator,
|
||||
IUpdateAssetValidation updateValidator)
|
||||
{
|
||||
_assetDataService = assetDataService;
|
||||
_createValidator = createValidator;
|
||||
_updateValidator = updateValidator;
|
||||
}
|
||||
|
||||
public async Task<AssetDTO?> GetAssetByIdAsync(int id)
|
||||
{
|
||||
var asset = await _assetDataService.GetByIdAsync(id);
|
||||
return asset == null ? null : MapToDTO(asset);
|
||||
}
|
||||
|
||||
public async Task<AssetDTO?> GetAssetByIdWithDetailsAsync(int id)
|
||||
{
|
||||
var asset = await _assetDataService.GetByIdWithDetailsAsync(id);
|
||||
return asset == null ? null : MapToDTO(asset);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AssetDTO>> GetAllAssetsAsync()
|
||||
{
|
||||
var assets = await _assetDataService.GetAllAsync();
|
||||
return assets.Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AssetDTO>> GetAssetsByLocationIdAsync(int locationId)
|
||||
{
|
||||
var assets = await _assetDataService.GetAllAsync();
|
||||
return assets.Where(a => a.LocationId == locationId).Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<PagedResult<AssetDTO>> GetAssetsPagedAsync(int page, int pageSize, string? search = null)
|
||||
{
|
||||
var (items, totalCount) = await _assetDataService.GetPagedAsync(page, pageSize, search);
|
||||
return new PagedResult<AssetDTO>
|
||||
{
|
||||
Items = items.Select(MapToDTO).ToList(),
|
||||
TotalCount = totalCount,
|
||||
Page = page,
|
||||
PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<AssetDTO> CreateAssetAsync(CreateAssetDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _createValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var asset = new Assets
|
||||
{
|
||||
Name = dto.Name,
|
||||
LocationId = dto.LocationId,
|
||||
CreatedDate = DateTime.UtcNow,
|
||||
createdby = userId
|
||||
};
|
||||
|
||||
var created = await _assetDataService.AddAsync(asset);
|
||||
return MapToDTO(created);
|
||||
}
|
||||
|
||||
public async Task<AssetDTO> UpdateAssetAsync(int id, UpdateAssetDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _updateValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var asset = await _assetDataService.GetByIdAsync(id);
|
||||
if (asset == null)
|
||||
throw new InvalidOperationException($"Asset with ID {id} not found");
|
||||
|
||||
if (dto.Name != null) asset.Name = dto.Name;
|
||||
if (dto.LocationId.HasValue) asset.LocationId = dto.LocationId;
|
||||
|
||||
asset.LastModificationTime = DateTime.UtcNow;
|
||||
if (int.TryParse(userId, out int userIdInt))
|
||||
asset.LastModifierUserId = userIdInt;
|
||||
|
||||
await _assetDataService.UpdateAsync(asset);
|
||||
return MapToDTO(asset);
|
||||
}
|
||||
|
||||
public async Task DeleteAssetAsync(int id, string userId)
|
||||
{
|
||||
var asset = await _assetDataService.GetByIdAsync(id);
|
||||
if (asset == null)
|
||||
throw new InvalidOperationException($"Asset with ID {id} not found");
|
||||
|
||||
asset.IsDeleted = true;
|
||||
asset.DeleterUserId = userId;
|
||||
asset.DeletionTime = DateTime.UtcNow;
|
||||
|
||||
await _assetDataService.UpdateAsync(asset);
|
||||
}
|
||||
|
||||
public async Task<bool> AssetExistsAsync(int id)
|
||||
{
|
||||
return await _assetDataService.ExistsAsync(id);
|
||||
}
|
||||
|
||||
public async Task<int> GetTotalAssetCountAsync()
|
||||
{
|
||||
return await _assetDataService.CountAsync();
|
||||
}
|
||||
|
||||
private AssetDTO MapToDTO(Assets asset)
|
||||
{
|
||||
return new AssetDTO
|
||||
{
|
||||
Id = asset.Id,
|
||||
Name = asset.Name,
|
||||
LocationId = asset.LocationId,
|
||||
LocationName = asset.Location?.Name,
|
||||
CreatedDate = asset.CreatedDate
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
231
SeaHaven.Services/Implementation/ContactService.cs
Normal file
231
SeaHaven.Services/Implementation/ContactService.cs
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.Validation;
|
||||
|
||||
namespace SeaHaven.Services.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Contact service implementation
|
||||
/// Contains business logic, validation, and orchestration for Contact operations
|
||||
/// </summary>
|
||||
public class ContactService : IContactService
|
||||
{
|
||||
private readonly IContactDataService _contactDataService;
|
||||
private readonly ICreateContactValidation _createValidator;
|
||||
private readonly IUpdateContactValidation _updateValidator;
|
||||
|
||||
public ContactService(
|
||||
IContactDataService contactDataService,
|
||||
ICreateContactValidation createValidator,
|
||||
IUpdateContactValidation updateValidator)
|
||||
{
|
||||
_contactDataService = contactDataService;
|
||||
_createValidator = createValidator;
|
||||
_updateValidator = updateValidator;
|
||||
}
|
||||
|
||||
// Query operations
|
||||
public async Task<ContactDTO?> GetContactByIdAsync(int id)
|
||||
{
|
||||
var contact = await _contactDataService.GetByIdAsync(id);
|
||||
return contact == null ? null : MapToDTO(contact);
|
||||
}
|
||||
|
||||
public async Task<ContactDTO?> GetContactByIdWithDetailsAsync(int id)
|
||||
{
|
||||
var contact = await _contactDataService.GetByIdWithDetailsAsync(id);
|
||||
return contact == null ? null : MapToDTO(contact);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ContactDTO>> GetAllContactsAsync()
|
||||
{
|
||||
var contacts = await _contactDataService.GetAllAsync();
|
||||
return contacts.Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ContactDTO>> GetContactsByAccountIdAsync(int accountId)
|
||||
{
|
||||
var contacts = await _contactDataService.GetByAccountIdAsync(accountId);
|
||||
return contacts.Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ContactDTO>> GetContactsByTypeAsync(string type)
|
||||
{
|
||||
var contacts = await _contactDataService.GetByTypeAsync(type);
|
||||
return contacts.Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<ContactDTO?> GetContactByEmailAsync(string email)
|
||||
{
|
||||
var contact = await _contactDataService.GetByEmailAsync(email);
|
||||
return contact == null ? null : MapToDTO(contact);
|
||||
}
|
||||
|
||||
// Paged results
|
||||
public async Task<PagedResult<ContactDTO>> GetContactsPagedAsync(
|
||||
int page,
|
||||
int pageSize,
|
||||
string? search = null,
|
||||
string? type = null)
|
||||
{
|
||||
var (items, totalCount) = await _contactDataService.GetPagedAsync(page, pageSize, search, type);
|
||||
|
||||
return new PagedResult<ContactDTO>
|
||||
{
|
||||
Items = items.Select(MapToDTO),
|
||||
TotalCount = totalCount,
|
||||
Page = page,
|
||||
PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
// Command operations
|
||||
public async Task<ContactDTO> CreateContactAsync(CreateContactDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _createValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
if (await _contactDataService.EmailExistsAsync(dto.Email))
|
||||
throw new InvalidOperationException($"Email '{dto.Email}' already exists");
|
||||
|
||||
var contact = new Contacts
|
||||
{
|
||||
FirstName = dto.FirstName,
|
||||
LastName = dto.LastName,
|
||||
Email = dto.Email,
|
||||
PhoneNumber = dto.Phone ?? dto.Mobile,
|
||||
Title = dto.Title,
|
||||
ContactType = dto.Type,
|
||||
AccountId = dto.AccountId,
|
||||
createdby = userId,
|
||||
CreatedDate = DateTime.Now
|
||||
};
|
||||
|
||||
var savedContact = await _contactDataService.AddAsync(contact);
|
||||
|
||||
return MapToDTO(savedContact);
|
||||
}
|
||||
|
||||
public async Task<ContactDTO> UpdateContactAsync(int id, UpdateContactDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _updateValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var existingContact = await _contactDataService.GetByIdAsync(id);
|
||||
if (existingContact == null)
|
||||
throw new KeyNotFoundException($"Contact with ID {id} not found");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(dto.Email) && dto.Email != existingContact.Email)
|
||||
{
|
||||
if (await _contactDataService.EmailExistsAsync(dto.Email, id))
|
||||
throw new InvalidOperationException($"Email '{dto.Email}' already exists");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(dto.FirstName))
|
||||
existingContact.FirstName = dto.FirstName;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(dto.LastName))
|
||||
existingContact.LastName = dto.LastName;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(dto.Email))
|
||||
existingContact.Email = dto.Email;
|
||||
|
||||
if (dto.Phone != null || dto.Mobile != null)
|
||||
existingContact.PhoneNumber = dto.Phone ?? dto.Mobile;
|
||||
|
||||
if (dto.Title != null)
|
||||
existingContact.Title = dto.Title;
|
||||
|
||||
if (dto.Type != null)
|
||||
existingContact.ContactType = dto.Type;
|
||||
|
||||
if (dto.AccountId.HasValue)
|
||||
existingContact.AccountId = dto.AccountId;
|
||||
|
||||
existingContact.LastModificationTime = DateTime.Now;
|
||||
|
||||
await _contactDataService.UpdateAsync(existingContact);
|
||||
|
||||
return MapToDTO(existingContact);
|
||||
}
|
||||
|
||||
public async Task DeleteContactAsync(int id, string userId)
|
||||
{
|
||||
// Check if exists
|
||||
if (!await _contactDataService.ExistsAsync(id))
|
||||
throw new KeyNotFoundException($"Contact with ID {id} not found");
|
||||
|
||||
// Business logic: Check if contact is being used (add your logic here)
|
||||
// For example, check if contact is linked to work orders, etc.
|
||||
|
||||
await _contactDataService.DeleteAsync(id);
|
||||
}
|
||||
|
||||
// Validation operations
|
||||
public async Task<bool> ContactExistsAsync(int id)
|
||||
{
|
||||
return await _contactDataService.ExistsAsync(id);
|
||||
}
|
||||
|
||||
public async Task<bool> EmailExistsAsync(string email, int? excludeId = null)
|
||||
{
|
||||
return await _contactDataService.EmailExistsAsync(email, excludeId);
|
||||
}
|
||||
|
||||
public async Task<bool> PhoneExistsAsync(string phone, int? excludeId = null)
|
||||
{
|
||||
return await _contactDataService.PhoneExistsAsync(phone, excludeId);
|
||||
}
|
||||
|
||||
// Statistics
|
||||
public async Task<int> GetTotalContactCountAsync()
|
||||
{
|
||||
return await _contactDataService.CountAsync();
|
||||
}
|
||||
|
||||
public async Task<int> GetContactCountByAccountAsync(int accountId)
|
||||
{
|
||||
return await _contactDataService.CountByAccountIdAsync(accountId);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, int>> GetContactCountByTypeAsync()
|
||||
{
|
||||
var contacts = await _contactDataService.GetAllAsync();
|
||||
return contacts
|
||||
.Where(c => !string.IsNullOrWhiteSpace(c.ContactType))
|
||||
.GroupBy(c => c.ContactType!)
|
||||
.ToDictionary(g => g.Key, g => g.Count());
|
||||
}
|
||||
|
||||
// Private mapping methods
|
||||
private static ContactDTO MapToDTO(Contacts contact)
|
||||
{
|
||||
return new ContactDTO
|
||||
{
|
||||
Id = contact.Id,
|
||||
FirstName = contact.FirstName,
|
||||
LastName = contact.LastName,
|
||||
Email = contact.Email,
|
||||
Phone = contact.PhoneNumber,
|
||||
Mobile = contact.PhoneNumber, // Contacts only has one PhoneNumber field
|
||||
Title = contact.Title,
|
||||
Type = contact.ContactType,
|
||||
AccountId = contact.AccountId,
|
||||
AccountName = contact.Account?.Name,
|
||||
CreatedDate = contact.CreatedDate,
|
||||
CreatedBy = contact.createdby,
|
||||
ModifiedDate = contact.LastModificationTime,
|
||||
ModifiedBy = null // Not tracked in FullAuditEntity
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
160
SeaHaven.Services/Implementation/DispatchService.cs
Normal file
160
SeaHaven.Services/Implementation/DispatchService.cs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.Validation;
|
||||
|
||||
namespace SeaHaven.Services.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Dispatch service implementation
|
||||
/// Contains business logic, validation, and orchestration for Dispatch operations
|
||||
/// </summary>
|
||||
public class DispatchService : IDispatchService
|
||||
{
|
||||
private readonly IDispatchDataService _dispatchDataService;
|
||||
private readonly ICreateDispatchValidation _createValidator;
|
||||
private readonly IUpdateDispatchValidation _updateValidator;
|
||||
|
||||
public DispatchService(
|
||||
IDispatchDataService dispatchDataService,
|
||||
ICreateDispatchValidation createValidator,
|
||||
IUpdateDispatchValidation updateValidator)
|
||||
{
|
||||
_dispatchDataService = dispatchDataService;
|
||||
_createValidator = createValidator;
|
||||
_updateValidator = updateValidator;
|
||||
}
|
||||
|
||||
public async Task<DispatchDTO?> GetDispatchByIdAsync(int id)
|
||||
{
|
||||
var dispatch = await _dispatchDataService.GetByIdAsync(id);
|
||||
return dispatch == null ? null : MapToDTO(dispatch);
|
||||
}
|
||||
|
||||
public async Task<DispatchDTO?> GetDispatchByIdWithDetailsAsync(int id)
|
||||
{
|
||||
var dispatch = await _dispatchDataService.GetByIdWithDetailsAsync(id);
|
||||
return dispatch == null ? null : MapToDTO(dispatch);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DispatchDTO>> GetAllDispatchesAsync()
|
||||
{
|
||||
var dispatches = await _dispatchDataService.GetAllAsync();
|
||||
return dispatches.Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DispatchDTO>> GetDispatchesByVendorIdAsync(int vendorId)
|
||||
{
|
||||
var dispatches = await _dispatchDataService.GetByVendorIdAsync(vendorId);
|
||||
return dispatches.Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<PagedResult<DispatchDTO>> GetDispatchesPagedAsync(int page, int pageSize, string? search = null, string? status = null)
|
||||
{
|
||||
var (items, totalCount) = await _dispatchDataService.GetPagedAsync(page, pageSize, search, status);
|
||||
return new PagedResult<DispatchDTO>
|
||||
{
|
||||
Items = items.Select(MapToDTO).ToList(),
|
||||
TotalCount = totalCount,
|
||||
Page = page,
|
||||
PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<DispatchDTO> CreateDispatchAsync(CreateDispatchDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _createValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var dispatchNumber = await GenerateNextDispatchNumberAsync();
|
||||
|
||||
var dispatch = new Dispatch
|
||||
{
|
||||
VendorId = dto.VendorId,
|
||||
ScheduledDate = dto.ScheduledDate,
|
||||
Status = dto.Status ?? "Pending",
|
||||
DispatchNumber = dispatchNumber,
|
||||
CreatedDate = DateTime.UtcNow,
|
||||
createdby = userId
|
||||
};
|
||||
|
||||
var created = await _dispatchDataService.AddAsync(dispatch);
|
||||
return MapToDTO(created);
|
||||
}
|
||||
|
||||
public async Task<DispatchDTO> UpdateDispatchAsync(int id, UpdateDispatchDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _updateValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var dispatch = await _dispatchDataService.GetByIdAsync(id);
|
||||
if (dispatch == null)
|
||||
throw new InvalidOperationException($"Dispatch with ID {id} not found");
|
||||
|
||||
if (dto.VendorId.HasValue) dispatch.VendorId = dto.VendorId.Value;
|
||||
if (dto.ScheduledDate.HasValue) dispatch.ScheduledDate = dto.ScheduledDate;
|
||||
if (dto.CompletedDate.HasValue) dispatch.CompletedDate = dto.CompletedDate;
|
||||
if (dto.Status != null) dispatch.Status = dto.Status;
|
||||
|
||||
dispatch.LastModificationTime = DateTime.UtcNow;
|
||||
if (int.TryParse(userId, out int userIdInt))
|
||||
dispatch.LastModifierUserId = userIdInt;
|
||||
|
||||
await _dispatchDataService.UpdateAsync(dispatch);
|
||||
return MapToDTO(dispatch);
|
||||
}
|
||||
|
||||
public async Task DeleteDispatchAsync(int id, string userId)
|
||||
{
|
||||
var dispatch = await _dispatchDataService.GetByIdAsync(id);
|
||||
if (dispatch == null)
|
||||
throw new InvalidOperationException($"Dispatch with ID {id} not found");
|
||||
|
||||
dispatch.IsDeleted = true;
|
||||
dispatch.DeleterUserId = userId;
|
||||
dispatch.DeletionTime = DateTime.UtcNow;
|
||||
|
||||
await _dispatchDataService.UpdateAsync(dispatch);
|
||||
}
|
||||
|
||||
public async Task<bool> DispatchExistsAsync(int id)
|
||||
{
|
||||
return await _dispatchDataService.ExistsAsync(id);
|
||||
}
|
||||
|
||||
public async Task<string> GenerateNextDispatchNumberAsync()
|
||||
{
|
||||
var count = await _dispatchDataService.CountAsync();
|
||||
return $"DISP-{(count + 1):D6}";
|
||||
}
|
||||
|
||||
public async Task<int> GetTotalDispatchCountAsync()
|
||||
{
|
||||
return await _dispatchDataService.CountAsync();
|
||||
}
|
||||
|
||||
private DispatchDTO MapToDTO(Dispatch dispatch)
|
||||
{
|
||||
return new DispatchDTO
|
||||
{
|
||||
Id = dispatch.Id,
|
||||
DispatchNumber = dispatch.DispatchNumber,
|
||||
Status = dispatch.Status,
|
||||
VendorId = dispatch.VendorId,
|
||||
VendorName = dispatch.Vendor?.CompanyName,
|
||||
ScheduledDate = dispatch.ScheduledDate,
|
||||
CompletedDate = dispatch.CompletedDate,
|
||||
CreatedDate = dispatch.CreatedDate,
|
||||
CreatedBy = dispatch.createdby
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
170
SeaHaven.Services/Implementation/EmployeeService.cs
Normal file
170
SeaHaven.Services/Implementation/EmployeeService.cs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.Validation;
|
||||
|
||||
namespace SeaHaven.Services.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Employee service implementation
|
||||
/// Contains business logic, validation, and orchestration for Employee operations
|
||||
/// </summary>
|
||||
public class EmployeeService : IEmployeeService
|
||||
{
|
||||
private readonly IEmployeeDataService _employeeDataService;
|
||||
private readonly ICreateEmployeeValidation _createValidator;
|
||||
private readonly IUpdateEmployeeValidation _updateValidator;
|
||||
|
||||
public EmployeeService(
|
||||
IEmployeeDataService employeeDataService,
|
||||
ICreateEmployeeValidation createValidator,
|
||||
IUpdateEmployeeValidation updateValidator)
|
||||
{
|
||||
_employeeDataService = employeeDataService;
|
||||
_createValidator = createValidator;
|
||||
_updateValidator = updateValidator;
|
||||
}
|
||||
|
||||
// Query operations
|
||||
public async Task<EmployeeDTO?> GetEmployeeByIdAsync(int id)
|
||||
{
|
||||
var employee = await _employeeDataService.GetByIdAsync(id);
|
||||
return employee == null ? null : MapToDTO(employee);
|
||||
}
|
||||
|
||||
public async Task<EmployeeDTO?> GetEmployeeByIdWithDetailsAsync(int id)
|
||||
{
|
||||
var employee = await _employeeDataService.GetByIdWithDetailsAsync(id);
|
||||
return employee == null ? null : MapToDTO(employee);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<EmployeeDTO>> GetAllEmployeesAsync()
|
||||
{
|
||||
var employees = await _employeeDataService.GetAllAsync();
|
||||
return employees.Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<PagedResult<EmployeeDTO>> GetEmployeesPagedAsync(int page, int pageSize, string? search = null)
|
||||
{
|
||||
var (items, totalCount) = await _employeeDataService.GetPagedAsync(page, pageSize, search);
|
||||
return new PagedResult<EmployeeDTO>
|
||||
{
|
||||
Items = items.Select(MapToDTO).ToList(),
|
||||
TotalCount = totalCount,
|
||||
Page = page,
|
||||
PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<EmployeeDTO> CreateEmployeeAsync(CreateEmployeeDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _createValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var employee = new Employee
|
||||
{
|
||||
FirstName = dto.FirstName,
|
||||
LastName = dto.LastName,
|
||||
DepartmentId = dto.DepartmentId,
|
||||
JobTitleId = dto.JobTitleId,
|
||||
RegionId = dto.RegionId,
|
||||
CreatedDate = DateTime.UtcNow,
|
||||
CreatedBy = userId
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(dto.Email))
|
||||
{
|
||||
employee.Emails.Add(new EmployeeEmail
|
||||
{
|
||||
Address = dto.Email,
|
||||
IsPrimary = true
|
||||
});
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(dto.Phone))
|
||||
{
|
||||
employee.Phones.Add(new EmployeePhone
|
||||
{
|
||||
Number = dto.Phone,
|
||||
IsPrimary = true
|
||||
});
|
||||
}
|
||||
|
||||
var created = await _employeeDataService.AddAsync(employee);
|
||||
return MapToDTO(created);
|
||||
}
|
||||
|
||||
public async Task<EmployeeDTO> UpdateEmployeeAsync(int id, UpdateEmployeeDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _updateValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var employee = await _employeeDataService.GetByIdAsync(id);
|
||||
if (employee == null)
|
||||
throw new InvalidOperationException($"Employee with ID {id} not found");
|
||||
|
||||
if (dto.FirstName != null) employee.FirstName = dto.FirstName;
|
||||
if (dto.LastName != null) employee.LastName = dto.LastName;
|
||||
if (dto.DepartmentId.HasValue) employee.DepartmentId = dto.DepartmentId;
|
||||
if (dto.JobTitleId.HasValue) employee.JobTitleId = dto.JobTitleId;
|
||||
if (dto.RegionId.HasValue) employee.RegionId = dto.RegionId;
|
||||
|
||||
employee.LastModificationTime = DateTime.UtcNow;
|
||||
employee.LastModifierUserId = int.Parse(userId);
|
||||
|
||||
await _employeeDataService.UpdateAsync(employee);
|
||||
return MapToDTO(employee);
|
||||
}
|
||||
|
||||
public async Task DeleteEmployeeAsync(int id, string userId)
|
||||
{
|
||||
var employee = await _employeeDataService.GetByIdAsync(id);
|
||||
if (employee == null)
|
||||
throw new InvalidOperationException($"Employee with ID {id} not found");
|
||||
|
||||
employee.IsDeleted = true;
|
||||
employee.CreatedBy = userId;
|
||||
employee.LastModificationTime = DateTime.UtcNow;
|
||||
|
||||
await _employeeDataService.UpdateAsync(employee);
|
||||
}
|
||||
|
||||
public async Task<bool> EmployeeExistsAsync(int id)
|
||||
{
|
||||
return await _employeeDataService.ExistsAsync(id);
|
||||
}
|
||||
|
||||
public async Task<int> GetTotalEmployeeCountAsync()
|
||||
{
|
||||
return await _employeeDataService.CountAsync();
|
||||
}
|
||||
|
||||
// Private helper methods
|
||||
private EmployeeDTO MapToDTO(Employee employee)
|
||||
{
|
||||
return new EmployeeDTO
|
||||
{
|
||||
Id = employee.Id,
|
||||
FirstName = employee.FirstName,
|
||||
LastName = employee.LastName,
|
||||
Email = employee.Emails?.FirstOrDefault(e => e.IsPrimary == true)?.Address,
|
||||
Phone = employee.Phones?.FirstOrDefault(p => p.IsPrimary == true)?.Number,
|
||||
DepartmentId = employee.DepartmentId,
|
||||
DepartmentName = employee.Department?.Name,
|
||||
JobTitleId = employee.JobTitleId,
|
||||
JobTitleName = employee.JobTitle?.Name,
|
||||
RegionId = employee.RegionId,
|
||||
RegionName = employee.Region?.Name,
|
||||
CreatedDate = employee.CreatedDate
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
171
SeaHaven.Services/Implementation/FollowUpService.cs
Normal file
171
SeaHaven.Services/Implementation/FollowUpService.cs
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.Validation;
|
||||
|
||||
namespace SeaHaven.Services.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Follow-Up service implementation
|
||||
/// Contains business logic, validation, and orchestration for Follow-Up operations
|
||||
/// </summary>
|
||||
public class FollowUpService : IFollowUpService
|
||||
{
|
||||
private readonly IFollowUpDataService _followUpDataService;
|
||||
private readonly ICreateFollowUpValidation _createValidator;
|
||||
private readonly IUpdateFollowUpValidation _updateValidator;
|
||||
|
||||
public FollowUpService(
|
||||
IFollowUpDataService followUpDataService,
|
||||
ICreateFollowUpValidation createValidator,
|
||||
IUpdateFollowUpValidation updateValidator)
|
||||
{
|
||||
_followUpDataService = followUpDataService;
|
||||
_createValidator = createValidator;
|
||||
_updateValidator = updateValidator;
|
||||
}
|
||||
|
||||
public async Task<FollowUpDTO?> GetFollowUpByIdAsync(int id)
|
||||
{
|
||||
var followUp = await _followUpDataService.GetByIdAsync(id);
|
||||
return followUp == null ? null : MapToDTO(followUp);
|
||||
}
|
||||
|
||||
public async Task<FollowUpDTO?> GetFollowUpByIdWithDetailsAsync(int id)
|
||||
{
|
||||
var followUp = await _followUpDataService.GetByIdWithDetailsAsync(id);
|
||||
return followUp == null ? null : MapToDTO(followUp);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<FollowUpDTO>> GetAllFollowUpsAsync()
|
||||
{
|
||||
var followUps = await _followUpDataService.GetAllAsync();
|
||||
return followUps.Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<FollowUpDTO>> GetFollowUpsByWorkOrderIdAsync(int workOrderId)
|
||||
{
|
||||
var followUps = await _followUpDataService.GetByWorkOrderIdAsync(workOrderId);
|
||||
return followUps.Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<PagedResult<FollowUpDTO>> GetFollowUpsPagedAsync(int page, int pageSize, string? search = null, string? status = null)
|
||||
{
|
||||
var (items, totalCount) = await _followUpDataService.GetPagedAsync(page, pageSize, search);
|
||||
|
||||
var filteredItems = items.AsEnumerable();
|
||||
if (!string.IsNullOrWhiteSpace(status))
|
||||
{
|
||||
filteredItems = filteredItems.Where(f => f.Status == status);
|
||||
}
|
||||
|
||||
return new PagedResult<FollowUpDTO>
|
||||
{
|
||||
Items = filteredItems.Select(MapToDTO).ToList(),
|
||||
TotalCount = totalCount,
|
||||
Page = page,
|
||||
PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<FollowUpDTO> CreateFollowUpAsync(CreateFollowUpDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _createValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var followUp = new FollowUps
|
||||
{
|
||||
Reason = dto.Reason,
|
||||
ScheduleDate = dto.ScheduleDate,
|
||||
ScheduleStartTime = dto.ScheduleStartTime,
|
||||
Status = dto.Status,
|
||||
EmployeeId = dto.EmployeeId,
|
||||
WorkOrderId = dto.WorkOrderId,
|
||||
AccountId = dto.AccountId,
|
||||
LocationId = dto.LocationId,
|
||||
CreatedDate = DateTime.UtcNow,
|
||||
createdby = userId
|
||||
};
|
||||
|
||||
var created = await _followUpDataService.AddAsync(followUp);
|
||||
return MapToDTO(created);
|
||||
}
|
||||
|
||||
public async Task<FollowUpDTO> UpdateFollowUpAsync(int id, UpdateFollowUpDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _updateValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var followUp = await _followUpDataService.GetByIdAsync(id);
|
||||
if (followUp == null)
|
||||
throw new InvalidOperationException($"Follow-Up with ID {id} not found");
|
||||
|
||||
if (dto.Reason != null) followUp.Reason = dto.Reason;
|
||||
if (dto.ScheduleDate.HasValue) followUp.ScheduleDate = dto.ScheduleDate.Value;
|
||||
if (dto.ScheduleStartTime.HasValue) followUp.ScheduleStartTime = dto.ScheduleStartTime.Value;
|
||||
if (dto.Status != null) followUp.Status = dto.Status;
|
||||
if (dto.EmployeeId.HasValue) followUp.EmployeeId = dto.EmployeeId;
|
||||
if (dto.WorkOrderId.HasValue) followUp.WorkOrderId = dto.WorkOrderId;
|
||||
if (dto.AccountId.HasValue) followUp.AccountId = dto.AccountId;
|
||||
if (dto.LocationId.HasValue) followUp.LocationId = dto.LocationId;
|
||||
|
||||
followUp.LastModificationTime = DateTime.UtcNow;
|
||||
if (int.TryParse(userId, out int userIdInt))
|
||||
followUp.LastModifierUserId = userIdInt;
|
||||
|
||||
await _followUpDataService.UpdateAsync(followUp);
|
||||
return MapToDTO(followUp);
|
||||
}
|
||||
|
||||
public async Task DeleteFollowUpAsync(int id, string userId)
|
||||
{
|
||||
var followUp = await _followUpDataService.GetByIdAsync(id);
|
||||
if (followUp == null)
|
||||
throw new InvalidOperationException($"Follow-Up with ID {id} not found");
|
||||
|
||||
followUp.IsDeleted = true;
|
||||
followUp.DeleterUserId = userId;
|
||||
followUp.DeletionTime = DateTime.UtcNow;
|
||||
|
||||
await _followUpDataService.UpdateAsync(followUp);
|
||||
}
|
||||
|
||||
public async Task<bool> FollowUpExistsAsync(int id)
|
||||
{
|
||||
return await _followUpDataService.ExistsAsync(id);
|
||||
}
|
||||
|
||||
public async Task<int> GetTotalFollowUpCountAsync()
|
||||
{
|
||||
return await _followUpDataService.CountAsync();
|
||||
}
|
||||
|
||||
private FollowUpDTO MapToDTO(FollowUps followUp)
|
||||
{
|
||||
return new FollowUpDTO
|
||||
{
|
||||
Id = followUp.Id,
|
||||
Reason = followUp.Reason,
|
||||
ScheduleDate = followUp.ScheduleDate,
|
||||
ScheduleStartTime = followUp.ScheduleStartTime,
|
||||
Status = followUp.Status,
|
||||
EmployeeId = followUp.EmployeeId,
|
||||
EmployeeName = followUp.Employee != null ? $"{followUp.Employee.FirstName} {followUp.Employee.LastName}" : null,
|
||||
WorkOrderId = followUp.WorkOrderId,
|
||||
AccountId = followUp.AccountId,
|
||||
AccountName = followUp.Account?.Name,
|
||||
LocationId = followUp.LocationId,
|
||||
LocationName = followUp.Location?.Name,
|
||||
CreatedDate = followUp.CreatedDate
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
163
SeaHaven.Services/Implementation/LocationService.cs
Normal file
163
SeaHaven.Services/Implementation/LocationService.cs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.Validation;
|
||||
|
||||
namespace SeaHaven.Services.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Location service implementation
|
||||
/// Contains business logic, validation, and orchestration for Location operations
|
||||
/// </summary>
|
||||
public class LocationService : ILocationService
|
||||
{
|
||||
private readonly ILocationDataService _locationDataService;
|
||||
private readonly ICreateLocationValidation _createValidator;
|
||||
private readonly IUpdateLocationValidation _updateValidator;
|
||||
|
||||
public LocationService(
|
||||
ILocationDataService locationDataService,
|
||||
ICreateLocationValidation createValidator,
|
||||
IUpdateLocationValidation updateValidator)
|
||||
{
|
||||
_locationDataService = locationDataService;
|
||||
_createValidator = createValidator;
|
||||
_updateValidator = updateValidator;
|
||||
}
|
||||
|
||||
// Query operations
|
||||
public async Task<LocationDTO?> GetLocationByIdAsync(int id)
|
||||
{
|
||||
var location = await _locationDataService.GetByIdAsync(id);
|
||||
return location == null ? null : MapToDTO(location);
|
||||
}
|
||||
|
||||
public async Task<LocationDTO?> GetLocationByIdWithDetailsAsync(int id)
|
||||
{
|
||||
var location = await _locationDataService.GetByIdWithDetailsAsync(id);
|
||||
return location == null ? null : MapToDTO(location);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LocationDTO>> GetAllLocationsAsync()
|
||||
{
|
||||
var locations = await _locationDataService.GetAllAsync();
|
||||
return locations.Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LocationDTO>> GetLocationsByAccountIdAsync(int accountId)
|
||||
{
|
||||
var locations = await _locationDataService.GetByAccountIdAsync(accountId);
|
||||
return locations.Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<PagedResult<LocationDTO>> GetLocationsPagedAsync(int page, int pageSize, string? search = null)
|
||||
{
|
||||
var (items, totalCount) = await _locationDataService.GetPagedAsync(page, pageSize, search);
|
||||
return new PagedResult<LocationDTO>
|
||||
{
|
||||
Items = items.Select(MapToDTO).ToList(),
|
||||
TotalCount = totalCount,
|
||||
Page = page,
|
||||
PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<LocationDTO> CreateLocationAsync(CreateLocationDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _createValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var location = new Locations
|
||||
{
|
||||
Name = dto.LocationName,
|
||||
Title = dto.LocationName,
|
||||
Address1 = dto.Address,
|
||||
City = dto.City,
|
||||
State = dto.State,
|
||||
Zip = dto.Zipcode,
|
||||
CreatedDate = DateTime.UtcNow,
|
||||
createdby = userId
|
||||
};
|
||||
|
||||
var created = await _locationDataService.AddAsync(location);
|
||||
return MapToDTO(created);
|
||||
}
|
||||
|
||||
public async Task<LocationDTO> UpdateLocationAsync(int id, UpdateLocationDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _updateValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var location = await _locationDataService.GetByIdAsync(id);
|
||||
if (location == null)
|
||||
throw new InvalidOperationException($"Location with ID {id} not found");
|
||||
|
||||
// Update properties
|
||||
if (dto.LocationName != null) location.Name = dto.LocationName;
|
||||
if (dto.LocationName != null) location.Title = dto.LocationName;
|
||||
if (dto.Address != null) location.Address1 = dto.Address;
|
||||
if (dto.City != null) location.City = dto.City;
|
||||
if (dto.State != null) location.State = dto.State;
|
||||
if (dto.Zipcode != null) location.Zip = dto.Zipcode;
|
||||
|
||||
location.LastModificationTime = DateTime.UtcNow;
|
||||
if (int.TryParse(userId, out int userIdInt))
|
||||
location.LastModifierUserId = userIdInt;
|
||||
|
||||
await _locationDataService.UpdateAsync(location);
|
||||
return MapToDTO(location);
|
||||
}
|
||||
|
||||
public async Task DeleteLocationAsync(int id, string userId)
|
||||
{
|
||||
var location = await _locationDataService.GetByIdAsync(id);
|
||||
if (location == null)
|
||||
throw new InvalidOperationException($"Location with ID {id} not found");
|
||||
|
||||
location.IsDeleted = true;
|
||||
location.DeleterUserId = userId;
|
||||
location.DeletionTime = DateTime.UtcNow;
|
||||
|
||||
await _locationDataService.UpdateAsync(location);
|
||||
}
|
||||
|
||||
public async Task<bool> LocationExistsAsync(int id)
|
||||
{
|
||||
return await _locationDataService.ExistsAsync(id);
|
||||
}
|
||||
|
||||
public async Task<int> GetTotalLocationCountAsync()
|
||||
{
|
||||
return await _locationDataService.CountAsync();
|
||||
}
|
||||
|
||||
// Private helper methods
|
||||
private LocationDTO MapToDTO(Locations location)
|
||||
{
|
||||
return new LocationDTO
|
||||
{
|
||||
Id = location.Id,
|
||||
Name = location.Name,
|
||||
Title = location.Title,
|
||||
Address1 = location.Address1,
|
||||
Address2 = location.Address2,
|
||||
City = location.City,
|
||||
State = location.State,
|
||||
Zip = location.Zip,
|
||||
PhoneNumber = location.PhoneNumber,
|
||||
Email = location.Email,
|
||||
Status = location.Status,
|
||||
CreatedDate = location.CreatedDate,
|
||||
CreatedBy = location.createdby
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
157
SeaHaven.Services/Implementation/PMScheduleService.cs
Normal file
157
SeaHaven.Services/Implementation/PMScheduleService.cs
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.Validation;
|
||||
|
||||
namespace SeaHaven.Services.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// PM Schedule service implementation
|
||||
/// Contains business logic, validation, and orchestration for PM Schedule operations
|
||||
/// </summary>
|
||||
public class PMScheduleService : IPMScheduleService
|
||||
{
|
||||
private readonly IPMScheduleDataService _pmScheduleDataService;
|
||||
private readonly ICreatePMScheduleValidation _createValidator;
|
||||
private readonly IUpdatePMScheduleValidation _updateValidator;
|
||||
|
||||
public PMScheduleService(
|
||||
IPMScheduleDataService pmScheduleDataService,
|
||||
ICreatePMScheduleValidation createValidator,
|
||||
IUpdatePMScheduleValidation updateValidator)
|
||||
{
|
||||
_pmScheduleDataService = pmScheduleDataService;
|
||||
_createValidator = createValidator;
|
||||
_updateValidator = updateValidator;
|
||||
}
|
||||
|
||||
public async Task<PMScheduleDTO?> GetPMScheduleByIdAsync(int id)
|
||||
{
|
||||
var pmSchedule = await _pmScheduleDataService.GetByIdAsync(id);
|
||||
return pmSchedule == null ? null : MapToDTO(pmSchedule);
|
||||
}
|
||||
|
||||
public async Task<PMScheduleDTO?> GetPMScheduleByIdWithDetailsAsync(int id)
|
||||
{
|
||||
var pmSchedule = await _pmScheduleDataService.GetByIdWithDetailsAsync(id);
|
||||
return pmSchedule == null ? null : MapToDTO(pmSchedule);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<PMScheduleDTO>> GetAllPMSchedulesAsync()
|
||||
{
|
||||
var pmSchedules = await _pmScheduleDataService.GetAllWithDetailsAsync();
|
||||
return pmSchedules.Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<PMScheduleDTO>> GetPMSchedulesByLocationIdAsync(int locationId)
|
||||
{
|
||||
var pmSchedules = await _pmScheduleDataService.GetAllWithDetailsAsync();
|
||||
return pmSchedules.Where(p => p.LocationId == locationId).Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<PagedResult<PMScheduleDTO>> GetPMSchedulesPagedAsync(int page, int pageSize, string? search = null)
|
||||
{
|
||||
var (items, totalCount) = await _pmScheduleDataService.GetPagedAsync(page, pageSize, search);
|
||||
return new PagedResult<PMScheduleDTO>
|
||||
{
|
||||
Items = items.Select(MapToDTO).ToList(),
|
||||
TotalCount = totalCount,
|
||||
Page = page,
|
||||
PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<PMScheduleDTO> CreatePMScheduleAsync(CreatePMScheduleDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _createValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var pmSchedule = new PMSchedules
|
||||
{
|
||||
Name = dto.Name,
|
||||
Frequency = dto.Frequency,
|
||||
StartDate = dto.StartDate,
|
||||
IsActive = dto.IsActive,
|
||||
AssetId = dto.AssetId,
|
||||
LocationId = dto.LocationId,
|
||||
CreatedDate = DateTime.UtcNow,
|
||||
createdby = userId
|
||||
};
|
||||
|
||||
var created = await _pmScheduleDataService.AddAsync(pmSchedule);
|
||||
return MapToDTO(created);
|
||||
}
|
||||
|
||||
public async Task<PMScheduleDTO> UpdatePMScheduleAsync(int id, UpdatePMScheduleDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _updateValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var pmSchedule = await _pmScheduleDataService.GetByIdAsync(id);
|
||||
if (pmSchedule == null)
|
||||
throw new InvalidOperationException($"PM Schedule with ID {id} not found");
|
||||
|
||||
if (dto.Name != null) pmSchedule.Name = dto.Name;
|
||||
if (dto.Frequency != null) pmSchedule.Frequency = dto.Frequency;
|
||||
if (dto.StartDate.HasValue) pmSchedule.StartDate = dto.StartDate.Value;
|
||||
if (dto.IsActive.HasValue) pmSchedule.IsActive = dto.IsActive.Value;
|
||||
if (dto.AssetId.HasValue) pmSchedule.AssetId = dto.AssetId;
|
||||
if (dto.LocationId.HasValue) pmSchedule.LocationId = dto.LocationId;
|
||||
|
||||
pmSchedule.LastModificationTime = DateTime.UtcNow;
|
||||
if (int.TryParse(userId, out int userIdInt))
|
||||
pmSchedule.LastModifierUserId = userIdInt;
|
||||
|
||||
await _pmScheduleDataService.UpdateAsync(pmSchedule);
|
||||
return MapToDTO(pmSchedule);
|
||||
}
|
||||
|
||||
public async Task DeletePMScheduleAsync(int id, string userId)
|
||||
{
|
||||
var pmSchedule = await _pmScheduleDataService.GetByIdAsync(id);
|
||||
if (pmSchedule == null)
|
||||
throw new InvalidOperationException($"PM Schedule with ID {id} not found");
|
||||
|
||||
pmSchedule.IsDeleted = true;
|
||||
pmSchedule.DeleterUserId = userId;
|
||||
pmSchedule.DeletionTime = DateTime.UtcNow;
|
||||
|
||||
await _pmScheduleDataService.UpdateAsync(pmSchedule);
|
||||
}
|
||||
|
||||
public async Task<bool> PMScheduleExistsAsync(int id)
|
||||
{
|
||||
return await _pmScheduleDataService.ExistsAsync(id);
|
||||
}
|
||||
|
||||
public async Task<int> GetTotalPMScheduleCountAsync()
|
||||
{
|
||||
return await _pmScheduleDataService.CountAsync();
|
||||
}
|
||||
|
||||
private PMScheduleDTO MapToDTO(PMSchedules pmSchedule)
|
||||
{
|
||||
return new PMScheduleDTO
|
||||
{
|
||||
Id = pmSchedule.Id,
|
||||
Name = pmSchedule.Name,
|
||||
Frequency = pmSchedule.Frequency,
|
||||
StartDate = pmSchedule.StartDate,
|
||||
IsActive = pmSchedule.IsActive,
|
||||
AssetId = pmSchedule.AssetId,
|
||||
AssetName = pmSchedule.Asset?.Name,
|
||||
LocationId = pmSchedule.LocationId,
|
||||
LocationName = pmSchedule.Location?.Name,
|
||||
CreatedDate = pmSchedule.CreatedDate
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
151
SeaHaven.Services/Implementation/VendorService.cs
Normal file
151
SeaHaven.Services/Implementation/VendorService.cs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using FluentValidation;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.Validation;
|
||||
|
||||
namespace SeaHaven.Services.Implementation
|
||||
{
|
||||
/// <summary>
|
||||
/// Vendor service implementation
|
||||
/// Contains business logic, validation, and orchestration for Vendor operations
|
||||
/// </summary>
|
||||
public class VendorService : IVendorService
|
||||
{
|
||||
private readonly IVendorDataService _vendorDataService;
|
||||
private readonly ICreateVendorValidation _createValidator;
|
||||
private readonly IUpdateVendorValidation _updateValidator;
|
||||
|
||||
public VendorService(
|
||||
IVendorDataService vendorDataService,
|
||||
ICreateVendorValidation createValidator,
|
||||
IUpdateVendorValidation updateValidator)
|
||||
{
|
||||
_vendorDataService = vendorDataService;
|
||||
_createValidator = createValidator;
|
||||
_updateValidator = updateValidator;
|
||||
}
|
||||
|
||||
public async Task<VendorDTO?> GetVendorByIdAsync(int id)
|
||||
{
|
||||
var vendor = await _vendorDataService.GetByIdAsync(id);
|
||||
return vendor == null ? null : MapToDTO(vendor);
|
||||
}
|
||||
|
||||
public async Task<VendorDTO?> GetVendorByIdWithDetailsAsync(int id)
|
||||
{
|
||||
var vendor = await _vendorDataService.GetByIdWithDetailsAsync(id);
|
||||
return vendor == null ? null : MapToDTO(vendor);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<VendorDTO>> GetAllVendorsAsync()
|
||||
{
|
||||
var vendors = await _vendorDataService.GetAllAsync();
|
||||
return vendors.Select(MapToDTO);
|
||||
}
|
||||
|
||||
public async Task<PagedResult<VendorDTO>> GetVendorsPagedAsync(int page, int pageSize, string? search = null)
|
||||
{
|
||||
var (items, totalCount) = await _vendorDataService.GetPagedAsync(page, pageSize, search);
|
||||
return new PagedResult<VendorDTO>
|
||||
{
|
||||
Items = items.Select(MapToDTO).ToList(),
|
||||
TotalCount = totalCount,
|
||||
Page = page,
|
||||
PageSize = pageSize
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<VendorDTO> CreateVendorAsync(CreateVendorDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _createValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var vendor = new Vendor
|
||||
{
|
||||
CompanyName = dto.Name,
|
||||
Email = dto.Email,
|
||||
Phone = dto.Phone,
|
||||
Address = dto.Address,
|
||||
City = dto.City,
|
||||
State = dto.State,
|
||||
Zip = dto.Zipcode,
|
||||
CreatedDate = DateTime.UtcNow,
|
||||
createdby = userId
|
||||
};
|
||||
|
||||
var created = await _vendorDataService.AddAsync(vendor);
|
||||
return MapToDTO(created);
|
||||
}
|
||||
|
||||
public async Task<VendorDTO> UpdateVendorAsync(int id, UpdateVendorDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _updateValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var vendor = await _vendorDataService.GetByIdAsync(id);
|
||||
if (vendor == null)
|
||||
throw new InvalidOperationException($"Vendor with ID {id} not found");
|
||||
|
||||
if (dto.Name != null) vendor.CompanyName = dto.Name;
|
||||
if (dto.Email != null) vendor.Email = dto.Email;
|
||||
if (dto.Phone != null) vendor.Phone = dto.Phone;
|
||||
if (dto.Address != null) vendor.Address = dto.Address;
|
||||
if (dto.City != null) vendor.City = dto.City;
|
||||
if (dto.State != null) vendor.State = dto.State;
|
||||
if (dto.Zipcode != null) vendor.Zip = dto.Zipcode;
|
||||
|
||||
vendor.LastModificationTime = DateTime.UtcNow;
|
||||
if (int.TryParse(userId, out int userIdInt))
|
||||
vendor.LastModifierUserId = userIdInt;
|
||||
|
||||
await _vendorDataService.UpdateAsync(vendor);
|
||||
return MapToDTO(vendor);
|
||||
}
|
||||
|
||||
public async Task DeleteVendorAsync(int id, string userId)
|
||||
{
|
||||
var vendor = await _vendorDataService.GetByIdAsync(id);
|
||||
if (vendor == null)
|
||||
throw new InvalidOperationException($"Vendor with ID {id} not found");
|
||||
|
||||
vendor.IsDeleted = true;
|
||||
vendor.DeleterUserId = userId;
|
||||
vendor.DeletionTime = DateTime.UtcNow;
|
||||
|
||||
await _vendorDataService.UpdateAsync(vendor);
|
||||
}
|
||||
|
||||
public async Task<bool> VendorExistsAsync(int id)
|
||||
{
|
||||
return await _vendorDataService.ExistsAsync(id);
|
||||
}
|
||||
|
||||
public async Task<int> GetTotalVendorCountAsync()
|
||||
{
|
||||
return await _vendorDataService.CountAsync();
|
||||
}
|
||||
|
||||
private VendorDTO MapToDTO(Vendor vendor)
|
||||
{
|
||||
return new VendorDTO
|
||||
{
|
||||
Id = vendor.Id,
|
||||
Name = vendor.CompanyName,
|
||||
Email = vendor.Email,
|
||||
Phone = vendor.Phone,
|
||||
Address = vendor.Address,
|
||||
City = vendor.City,
|
||||
State = vendor.State,
|
||||
Zipcode = vendor.Zip
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
168
SeaHaven.Services/Implementation/WorkOrderService.cs
Normal file
168
SeaHaven.Services/Implementation/WorkOrderService.cs
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using Data.SeaHavenIndustries.Enums;
|
||||
using FluentValidation;
|
||||
using SeaHaven.DataServices.Interfaces;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.Services.Interfaces;
|
||||
using SeaHaven.Services.Validation;
|
||||
|
||||
namespace SeaHaven.Services.Implementation
|
||||
{
|
||||
public class WorkOrderService : IWorkOrderService
|
||||
{
|
||||
private readonly IWorkOrderDataService _workOrderDataService;
|
||||
private readonly ICreateWorkOrderValidation _createValidator;
|
||||
private readonly IUpdateWorkOrderValidation _updateValidator;
|
||||
|
||||
public WorkOrderService(
|
||||
IWorkOrderDataService workOrderDataService,
|
||||
ICreateWorkOrderValidation createValidator,
|
||||
IUpdateWorkOrderValidation updateValidator)
|
||||
{
|
||||
_workOrderDataService = workOrderDataService;
|
||||
_createValidator = createValidator;
|
||||
_updateValidator = updateValidator;
|
||||
}
|
||||
|
||||
public async Task<WorkOrderDTO?> GetWorkOrderByIdAsync(int id)
|
||||
{
|
||||
var workOrder = await _workOrderDataService.GetByIdAsync(id);
|
||||
return workOrder == null ? null : WorkOrderDTO.MapToModel(workOrder);
|
||||
}
|
||||
|
||||
public async Task<WorkOrderDTO?> GetWorkOrderByIdWithDetailsAsync(int id)
|
||||
{
|
||||
var workOrder = await _workOrderDataService.GetByIdWithDetailsAsync(id);
|
||||
return workOrder == null ? null : WorkOrderDTO.MapToModel(workOrder);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<WorkOrderDTO>> GetAllWorkOrdersAsync()
|
||||
{
|
||||
var workOrders = await _workOrderDataService.GetAllAsync();
|
||||
return workOrders.Select(WorkOrderDTO.MapToModel);
|
||||
}
|
||||
|
||||
public async Task<PagedResult<WorkOrderDTO>> GetWorkOrdersPagedAsync(int page, int pageSize, string? search = null, string? status = null, string? priority = null)
|
||||
{
|
||||
var (items, totalCount) = await _workOrderDataService.GetPagedAsync(page, pageSize, search, status, priority);
|
||||
return new PagedResult<WorkOrderDTO>
|
||||
{
|
||||
Items = items.Select(WorkOrderDTO.MapToModel),
|
||||
TotalCount = totalCount,
|
||||
Page = page,
|
||||
PageSize = pageSize
|
||||
};
|
||||
}
|
||||
public async Task<int> CreateWorkOrderAsync(CreateWorkOrderDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _createValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var workOrder = CreateWorkOrderDTO.MapToEntity(dto);
|
||||
workOrder.createdby = userId;
|
||||
workOrder.CreatedDate = DateTime.UtcNow;
|
||||
workOrder.InternalWONumber = await _workOrderDataService.GenerateNextInternalWONumberAsync();
|
||||
|
||||
var saved = await _workOrderDataService.AddAsync(workOrder);
|
||||
|
||||
await LogAuditAsync(saved.Id, userId, AuditAction.Create, "Work order created");
|
||||
|
||||
return saved.Id;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateWorkOrderAsync(int id, UpdateWorkOrderDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _updateValidator.ValidateAsync(dto);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
|
||||
var existingWorkOrder = await _workOrderDataService.GetByIdAsync(id);
|
||||
if (existingWorkOrder == null)
|
||||
return false;
|
||||
|
||||
var changes = new List<string>();
|
||||
changes.AddRange(TrackChanges(existingWorkOrder, dto));
|
||||
|
||||
UpdateWorkOrderDTO.UpdateEntity(dto, existingWorkOrder);
|
||||
await _workOrderDataService.UpdateAsync(existingWorkOrder);
|
||||
|
||||
if (changes.Any())
|
||||
{
|
||||
await LogAuditAsync(id, userId, AuditAction.Edit, string.Join(", ", changes));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteWorkOrderAsync(int id, string userId)
|
||||
{
|
||||
if (!await _workOrderDataService.ExistsAsync(id))
|
||||
return false;
|
||||
|
||||
await _workOrderDataService.DeleteWorkOrderAttachmentsAsync(id);
|
||||
await _workOrderDataService.DeleteWorkOrderContactsAsync(id);
|
||||
await _workOrderDataService.DeleteWorkOrderCategoriesAsync(id);
|
||||
await _workOrderDataService.DeleteAsync(id);
|
||||
|
||||
await LogAuditAsync(id, userId, AuditAction.Delete, "Work order deleted");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> WorkOrderExistsAsync(int id)
|
||||
{
|
||||
return await _workOrderDataService.ExistsAsync(id);
|
||||
}
|
||||
|
||||
public async Task<string> GenerateNextInternalWONumberAsync()
|
||||
{
|
||||
return await _workOrderDataService.GenerateNextInternalWONumberAsync();
|
||||
}
|
||||
|
||||
public async Task<int> GetTotalWorkOrderCountAsync()
|
||||
{
|
||||
return await _workOrderDataService.CountAsync();
|
||||
}
|
||||
|
||||
private List<string> TrackChanges(WorkOrder existing, UpdateWorkOrderDTO dto)
|
||||
{
|
||||
var changes = new List<string>();
|
||||
|
||||
if (dto.WorkerOrderTitle != null && dto.WorkerOrderTitle != existing.WorkerOrderTitle)
|
||||
changes.Add($"Title: {existing.WorkerOrderTitle} → {dto.WorkerOrderTitle}");
|
||||
|
||||
if (dto.Status.HasValue && dto.Status.ToString() != existing.Status)
|
||||
changes.Add($"Status: {existing.Status} → {dto.Status}");
|
||||
|
||||
if (dto.Priority.HasValue && dto.Priority.ToString() != existing.Priority)
|
||||
changes.Add($"Priority: {existing.Priority} → {dto.Priority}");
|
||||
|
||||
if (dto.AssignTo != null && dto.AssignTo != existing.AssignTo)
|
||||
changes.Add($"Assigned To: {existing.AssignTo} → {dto.AssignTo}");
|
||||
|
||||
if (dto.DueDate.HasValue && dto.DueDate != existing.DueDate)
|
||||
changes.Add($"Due Date: {existing.DueDate:yyyy-MM-dd} → {dto.DueDate:yyyy-MM-dd}");
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
private async Task LogAuditAsync(int workOrderId, string userId, AuditAction action, string description)
|
||||
{
|
||||
var auditLog = new WorkOrderAuditLog
|
||||
{
|
||||
WorkOrderId = workOrderId,
|
||||
Action = action.ToString(),
|
||||
FieldName = description,
|
||||
UserId = userId,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _workOrderDataService.AddAuditLogAsync(auditLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
SeaHaven.Services/Interfaces/IAccountService.cs
Normal file
17
SeaHaven.Services/Interfaces/IAccountService.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using SeaHaven.Services.DTOs;
|
||||
|
||||
namespace SeaHaven.Services.Interfaces
|
||||
{
|
||||
public interface IAccountService
|
||||
{
|
||||
Task<AccountDTO?> GetAccountByIdAsync(int id);
|
||||
Task<AccountDTO?> GetAccountByIdWithDetailsAsync(int id);
|
||||
Task<IEnumerable<AccountDTO>> GetAllAccountsAsync();
|
||||
Task<PagedResult<AccountDTO>> GetAccountsPagedAsync(int page, int pageSize, string? search = null);
|
||||
Task<AccountDTO> CreateAccountAsync(CreateAccountDTO dto, string userId);
|
||||
Task<AccountDTO> UpdateAccountAsync(int id, UpdateAccountDTO dto, string userId);
|
||||
Task DeleteAccountAsync(int id, string userId);
|
||||
Task<bool> AccountExistsAsync(int id);
|
||||
Task<int> GetTotalAccountCountAsync();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue