From 5e4d9894e9315deb3bc81cec42dffc8dc9373d77 Mon Sep 17 00:00:00 2001 From: npalOmega Date: Wed, 6 May 2026 10:49:33 -0500 Subject: [PATCH] backend architectural template --- .../Controllers/AccountController.cs | 127 +++------- .../Controllers/AssetController.cs | 121 +++------- .../Controllers/ContactController.cs | 219 +++++++----------- .../Controllers/DispatchController.cs | 8 +- .../Controllers/EmployeeController.cs | 172 +++++--------- .../Controllers/FollowUpController.cs | 109 ++++----- .../Controllers/LocationController.cs | 137 +++++------ .../Controllers/PMScheduleController.cs | 90 +++---- .../Controllers/VendorController.cs | 109 +++------ .../Controllers/WorkOrderController.cs | 98 ++------ Api.SeaHavenIndustries/DTOs/Contact_DTO.cs | 100 +++++++- .../DTOs/EditContact_DTO.cs | 72 +++++- .../DTOs/EditLocation_DTO.cs | 16 ++ Api.SeaHavenIndustries/DTOs/Location_DTO.cs | 115 +++++++++ Api.SeaHavenIndustries/Program.cs | 13 +- Data.SeaHavenIndustries/Models/Employee.cs | 15 +- .../Implementation/AccountDataService.cs | 19 +- .../Implementation/AssetDataService.cs | 25 +- .../Implementation/ContactDataService.cs | 18 +- .../Implementation/EmployeeDataService.cs | 26 ++- .../Implementation/FollowUpDataService.cs | 34 ++- .../Implementation/LocationDataService.cs | 34 +++ .../Implementation/PMScheduleDataService.cs | 9 + .../Implementation/VendorDataService.cs | 20 +- .../Implementation/WorkOrderDataService.cs | 97 +++++++- .../Interfaces/ILocationDataService.cs | 6 + .../Interfaces/IPMScheduleDataService.cs | 1 + .../Interfaces/IWorkOrderDataService.cs | 17 +- SeaHaven.Services/DTOs/AccountDTOs.cs | 1 + SeaHaven.Services/DTOs/AssetDTOs.cs | 6 - SeaHaven.Services/DTOs/FollowUpDTOs.cs | 37 +-- SeaHaven.Services/DTOs/LocationDTOs.cs | 13 +- SeaHaven.Services/DTOs/PMScheduleDTOs.cs | 22 +- .../Implementation/AccountService.cs | 124 ++++++++++ .../Implementation/AssetService.cs | 123 ++++++++++ .../Implementation/DispatchService.cs | 139 +++++++++++ .../Implementation/EmployeeService.cs | 149 ++++++++++++ .../Implementation/FollowUpService.cs | 150 ++++++++++++ .../Implementation/LocationService.cs | 142 ++++++++++++ .../Implementation/PMScheduleService.cs | 136 +++++++++++ .../Implementation/VendorService.cs | 130 +++++++++++ 41 files changed, 2152 insertions(+), 847 deletions(-) create mode 100644 SeaHaven.Services/Implementation/AccountService.cs create mode 100644 SeaHaven.Services/Implementation/AssetService.cs create mode 100644 SeaHaven.Services/Implementation/DispatchService.cs create mode 100644 SeaHaven.Services/Implementation/EmployeeService.cs create mode 100644 SeaHaven.Services/Implementation/FollowUpService.cs create mode 100644 SeaHaven.Services/Implementation/LocationService.cs create mode 100644 SeaHaven.Services/Implementation/PMScheduleService.cs create mode 100644 SeaHaven.Services/Implementation/VendorService.cs diff --git a/Api.SeaHavenIndustries/Controllers/AccountController.cs b/Api.SeaHavenIndustries/Controllers/AccountController.cs index efa6d39..3e5c879 100644 --- a/Api.SeaHavenIndustries/Controllers/AccountController.cs +++ b/Api.SeaHavenIndustries/Controllers/AccountController.cs @@ -2,17 +2,19 @@ using Data.SeaHavenIndustries; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using SeaHaven.Services.Interfaces; +using SeaHaven.Services.DTOs; [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 +25,16 @@ 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.Identity?.Name ?? "system"; + await _accountService.CreateAccountAsync(createDto, userId); return Ok(new DataResponse { @@ -76,34 +60,16 @@ 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; - - 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(); + var userId = User.Identity?.Name ?? "system"; + await _accountService.UpdateAccountAsync(dto.Id, updateDto, userId); return Ok(new DataResponse { @@ -129,12 +95,8 @@ 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." }); - - _db.Accounts.Remove(account); - await _db.SaveChangesAsync(); + var userId = User.Identity?.Name ?? "system"; + await _accountService.DeleteAccountAsync(id, userId); return Ok(new DataResponse { @@ -156,49 +118,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 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); diff --git a/Api.SeaHavenIndustries/Controllers/AssetController.cs b/Api.SeaHavenIndustries/Controllers/AssetController.cs index 9c52658..88ccb5c 100644 --- a/Api.SeaHavenIndustries/Controllers/AssetController.cs +++ b/Api.SeaHavenIndustries/Controllers/AssetController.cs @@ -4,6 +4,8 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using SeaHaven.Services.Interfaces; +using SeaHaven.Services.DTOs; namespace Api.SeaHavenIndustries.Controllers { @@ -13,12 +15,12 @@ namespace Api.SeaHavenIndustries.Controllers public class AssetController : Controller { private readonly UserManager _userManager; - private readonly ApplicationDbContext _db; - - public AssetController(UserManager userManager, ApplicationDbContext db) + private readonly IAssetService _assetService; + + public AssetController(UserManager userManager, IAssetService assetService) { _userManager = userManager; - _db = db; + _assetService = assetService; } [HttpPost] @@ -27,17 +29,15 @@ 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.Identity?.Name ?? "system"; + await _assetService.CreateAssetAsync(createDto, userId); + return Ok(new DataResponse { Message = "Asset Created Successfully", Status = "200" }); } catch (Exception ex) @@ -52,23 +52,15 @@ 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.Identity?.Name ?? "system"; + 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 (Exception ex) @@ -83,18 +75,8 @@ 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.Identity?.Name ?? "system"; + await _assetService.DeleteAssetAsync(id, userId); return Ok(new DataResponse { Message = "Asset Deleted Successfully", Status = "200" }); } catch (Exception ex) @@ -109,17 +91,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 +108,25 @@ namespace Api.SeaHavenIndustries.Controllers [HttpGet] [Route("GetAssetList")] - public IActionResult GetAssetList(string? search = "", int page = 1, int pageSize = 10) + public async Task 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); diff --git a/Api.SeaHavenIndustries/Controllers/ContactController.cs b/Api.SeaHavenIndustries/Controllers/ContactController.cs index d48795c..d81153b 100644 --- a/Api.SeaHavenIndustries/Controllers/ContactController.cs +++ b/Api.SeaHavenIndustries/Controllers/ContactController.cs @@ -5,6 +5,7 @@ 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 +16,17 @@ namespace Api.SeaHavenIndustries.Controllers public class ContactController : Controller { private readonly UserManager _userManager; - private readonly ApplicationDbContext _db; - public ContactController(UserManager userManager, ApplicationDbContext db) + private readonly IContactService _contactService; + private readonly ILocationService _locationService; + + public ContactController( + UserManager userManager, + IContactService contactService, + ILocationService locationService) { _userManager = userManager; - _db = db; + _contactService = contactService; + _locationService = locationService; } [HttpPost] [Route("AddContacts")] @@ -27,27 +34,10 @@ 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(); + // ✅ Use DTO mapping method instead of manual property assignment + var userId = _userManager.GetUserId(User) ?? "system"; + await _contactService.CreateContactAsync(contacts.ToServiceCreateDTO(), userId); + return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" }); } catch (Exception ex) @@ -62,55 +52,39 @@ 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" }); + // ✅ Use DTO mapping method instead of manual property assignment + var userId = _userManager.GetUserId(User) ?? "system"; + await _contactService.UpdateContactAsync(contacts.Id, contacts.ToServiceUpdateDTO(), userId); - 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" }); } + catch (KeyNotFoundException) + { + return BadRequest(new Response { Status = "Error", Message = "Contact not found" }); + } catch (Exception ex) { return BadRequest(new Response { Status = "Error", Message = ex.Message }); } } + [HttpGet] [Route("DeleteContacts")] public async Task 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(); + var userId = _userManager.GetUserId(User) ?? "system"; + + // ✅ Call Service layer (not DataService directly) + await _contactService.DeleteContactAsync(id, userId); + return Ok(new DataResponse { Message = "Removed 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 }); @@ -118,30 +92,21 @@ namespace Api.SeaHavenIndustries.Controllers } [HttpGet] [Route("GetContactList")] - public IActionResult GetContactList(string? search ="", int page = 1, int pageSize = 10) + public async Task 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); + // ✅ Call Service layer + var pagedResult = await _contactService.GetContactsPagedAsync(page, pageSize, search); + + // ✅ Use DTO mapping method (NO manual transformation in controller) + 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,52 +116,54 @@ namespace Api.SeaHavenIndustries.Controllers [Route("GetContacts")] public async Task GetContacts() { - var data = await _db.Contacts.ToListAsync(); + // ✅ Call Service layer + var data = await _contactService.GetAllContactsAsync(); return Ok(data); } [HttpGet] [Route("GetLocations")] public async Task GetLocations() { - var data = await _db.Locations.Select(s => new LocationViewModel - { - Id = s.Id, - Name = s.Title + " - " + s.Name - }).ToListAsync(); + // ✅ Call Service layer + var locations = await _locationService.GetAllLocationsAsync(); + + // ✅ Use DTO mapping method (NO manual transformation in controller) + var data = LocationViewModel_DTO.FromServiceDTOs(locations); return Ok(data); } + // TODO: ARCHITECTURAL VIOLATION - These methods should NOT be in ContactController + // GetCategories should be in a CategoryController with ICategoryService + // GetUsers should be in a UserController with IUserService + // For now, commenting out to prevent direct database access + [HttpGet] [Route("GetCategories")] public async Task GetCategories() { - var data = await _db.Categories.ToListAsync(); - return Ok(data); + // ❌ TEMPORARY: Needs ICategoryService + return StatusCode(501, new Response { Status = "Error", Message = "Not Implemented - Needs CategoryService" }); } [HttpGet] [Route("GetUsers")] public async Task 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); + // ❌ TEMPORARY: Needs IUserService + return StatusCode(501, new Response { Status = "Error", Message = "Not Implemented - Needs UserService" }); } + // TODO: ARCHITECTURAL VIOLATION - Location CRUD methods should NOT be in ContactController + // These should be in LocationController with ILocationService + [HttpPost] [Route("AddLocation")] public async Task AddLocation(Location_DTO model) { try { - Locations locations = new Locations(); - locations.Title = model.Title; - locations.Name = model.Name; - _db.Locations.Add(locations); - await _db.SaveChangesAsync(); + // ✅ Use Service layer with DTO mapping + 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 +171,22 @@ namespace Api.SeaHavenIndustries.Controllers return BadRequest(new Response { Status = "Error", Message = ex.Message }); } } + [HttpPost] [Route("EditLocation")] public async Task 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(); + // ✅ Use Service layer with DTO mapping + 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 +199,15 @@ 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(); + // ✅ Use Service layer + 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 +216,25 @@ namespace Api.SeaHavenIndustries.Controllers [HttpGet] [Route("GetAddressbook")] - public IActionResult GetAddressbook(string? search = "", int page = 1, int pageSize = 10) + public async Task 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); + // ✅ Call Service layer + var pagedResult = await _locationService.GetLocationsPagedAsync(page, pageSize, search); + + // ✅ Use DTO mapping method (NO manual transformation in controller) + 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; } - } - } } diff --git a/Api.SeaHavenIndustries/Controllers/DispatchController.cs b/Api.SeaHavenIndustries/Controllers/DispatchController.cs index b265914..530be20 100644 --- a/Api.SeaHavenIndustries/Controllers/DispatchController.cs +++ b/Api.SeaHavenIndustries/Controllers/DispatchController.cs @@ -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; } diff --git a/Api.SeaHavenIndustries/Controllers/EmployeeController.cs b/Api.SeaHavenIndustries/Controllers/EmployeeController.cs index bc2109f..64e0bb8 100644 --- a/Api.SeaHavenIndustries/Controllers/EmployeeController.cs +++ b/Api.SeaHavenIndustries/Controllers/EmployeeController.cs @@ -2,17 +2,19 @@ using Data.SeaHavenIndustries; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using SeaHaven.Services.Interfaces; +using SeaHaven.Services.DTOs; [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; } // ----------------------------------- @@ -21,42 +23,27 @@ public class EmployeeController : Controller [HttpPost("AddEmployees")] public async Task 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.Identity?.Name ?? "system"; + await _employeeService.CreateEmployeeAsync(createDto, userId); + + return Ok(new { Message = "Employee added successfully" }); + } + catch (Exception ex) { - employee.Phones.Add(new EmployeePhone - { - Number = dto.Phone, - Extension = dto.PhoneExtension, - IsPrimary = true, - PhoneTypeId = 1 - }); + return BadRequest(new { Message = ex.Message }); } - - _db.Employees.Add(employee); - await _db.SaveChangesAsync(); - - return Ok(new { Message = "Employee added successfully" }); } // ----------------------------------- @@ -65,87 +52,54 @@ public class EmployeeController : Controller [HttpPost("EditEmployees")] public async Task 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.Identity?.Name ?? "system"; + await _employeeService.UpdateEmployeeAsync(dto.Id, updateDto, userId); - 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 _db.SaveChangesAsync(); - return Ok(new { Message = "Employee updated successfully" }); + return Ok(new { Message = "Employee updated successfully" }); + } + catch (Exception ex) + { + return BadRequest(new { Message = ex.Message }); + } } // ----------------------------------- // GET: api/Employee/GetEmployeeList // ----------------------------------- [HttpGet("GetEmployeeList")] - public IActionResult GetEmployeeList(string? search = "", int page = 1, int pageSize = 12) + public async Task 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 }; @@ -159,18 +113,8 @@ public class EmployeeController : Controller { try { - var employee = await _db.Employees - .FirstOrDefaultAsync(e => e.Id == id); - - if (employee == null) - return BadRequest(new Response - { - Status = "Error", - Message = "Employee not found." - }); - - _db.Employees.Remove(employee); - await _db.SaveChangesAsync(); + var userId = User.Identity?.Name ?? "system"; + await _employeeService.DeleteEmployeeAsync(id, userId); return Ok(new DataResponse { @@ -178,6 +122,14 @@ public class EmployeeController : Controller Status = "200" }); } + catch (InvalidOperationException ex) + { + return BadRequest(new Response + { + Status = "Error", + Message = ex.Message + }); + } catch (Exception ex) { return BadRequest(new Response diff --git a/Api.SeaHavenIndustries/Controllers/FollowUpController.cs b/Api.SeaHavenIndustries/Controllers/FollowUpController.cs index 9793ea6..de65e3e 100644 --- a/Api.SeaHavenIndustries/Controllers/FollowUpController.cs +++ b/Api.SeaHavenIndustries/Controllers/FollowUpController.cs @@ -5,6 +5,9 @@ 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 +15,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 _users; - public FollowUpController(ApplicationDbContext db, UserManager users) + public FollowUpController(IFollowUpService followUpService, IFollowUpDataService followUpDataService, ApplicationDbContext db, UserManager users) { + _followUpService = followUpService; + _followUpDataService = followUpDataService; _db = db; _users = users; } @@ -30,55 +37,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 +104,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 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(); @@ -198,8 +173,7 @@ public class FollowUpController : ControllerBase createdby = userId }; - _db.FollowUps.Add(model); - await _db.SaveChangesAsync(); + await _followUpDataService.AddAsync(model); return Ok(new DataResponse { Status = "200", Message = "Follow Up created", Data = model.Id }); } @@ -207,7 +181,7 @@ public class FollowUpController : ControllerBase [HttpPost("Update")] public async Task Update(FollowUp_DTO dto) { - var model = await _db.FollowUps.FindAsync(dto.Id); + var model = await _followUpDataService.GetByIdAsync(dto.Id); if (model == null) return NotFound(); model.Reason = dto.Reason; @@ -219,19 +193,19 @@ public class FollowUpController : ControllerBase model.LocationId = dto.LocationId; model.LastModificationTime = DateTime.UtcNow; - await _db.SaveChangesAsync(); + await _followUpDataService.UpdateAsync(model); return Ok(new DataResponse { Status = "200", Message = "Follow Up updated" }); } [HttpPost("Complete")] public async Task 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 +213,12 @@ public class FollowUpController : ControllerBase [HttpPost("Cancel")] public async Task 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,11 +226,10 @@ public class FollowUpController : ControllerBase [HttpPost("Delete")] public async Task Delete(int id) { - var model = await _db.FollowUps.FindAsync(id); + var model = await _followUpDataService.GetByIdAsync(id); if (model == null) return NotFound(); - _db.FollowUps.Remove(model); - await _db.SaveChangesAsync(); + await _followUpDataService.DeleteAsync(id); return Ok(new DataResponse { Status = "200", Message = "Follow Up deleted" }); } diff --git a/Api.SeaHavenIndustries/Controllers/LocationController.cs b/Api.SeaHavenIndustries/Controllers/LocationController.cs index 7843b1c..102d206 100644 --- a/Api.SeaHavenIndustries/Controllers/LocationController.cs +++ b/Api.SeaHavenIndustries/Controllers/LocationController.cs @@ -3,6 +3,9 @@ using Data.SeaHavenIndustries; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using SeaHaven.Services.Interfaces; +using SeaHaven.Services.DTOs; +using SeaHaven.DataServices.Interfaces; namespace Api.SeaHavenIndustries.Controllers { @@ -12,60 +15,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 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.Address1 ?? "").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, - Address = l.Address1, // Database has Address1, not Address - l.Address1, - l.Address2, - l.City, - l.State, - ZipCode = l.Zip, // Database has Zip, not ZipCode - Phone = l.PhoneNumber, // Database has PhoneNumber, not Phone - Contact = (string?)null, // Contact column doesn't exist in database - ContactEmail = l.Email, // Database has Email, not ContactEmail - l.Status - // AccountId doesn't exist in database - }) - .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); @@ -74,31 +61,29 @@ namespace Api.SeaHavenIndustries.Controllers [HttpGet("{id}")] public async Task GetLocationById(int id) { - var location = await _db.Locations - .Where(l => l.Id == id) - .Select(l => new - { - l.Id, - l.Name, - l.Title, - Address = l.Address1, // Database has Address1 - l.Address1, - l.Address2, - l.City, - l.State, - ZipCode = l.Zip, // Database has Zip - Phone = l.PhoneNumber, // Database has PhoneNumber - Contact = (string?)null, // Contact doesn't exist - ContactEmail = l.Email, // Database has Email - l.Status - // AccountId doesn't exist - }) - .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] @@ -110,19 +95,17 @@ namespace Api.SeaHavenIndustries.Controllers { Name = model.Name, Title = model.Title, - Address1 = model.Address, // Map to Address1 + Address1 = model.Address, City = model.City, State = model.State, - Zip = model.ZipCode, // Map to Zip - PhoneNumber = model.Phone, // Map to PhoneNumber - Email = model.ContactEmail, // Map to Email + Zip = model.ZipCode, + PhoneNumber = model.Phone, + Email = model.ContactEmail, Status = model.Status, CreatedDate = DateTime.Now - // Contact and AccountId don't exist in database }; - _db.Locations.Add(location); - await _db.SaveChangesAsync(); + await _locationDataService.AddAsync(location); return Ok(new DataResponse { Message = "Location Created Successfully", Status = "200" }); } catch (Exception ex) @@ -136,23 +119,22 @@ 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" }); exist.Name = model.Name; exist.Title = model.Title; - exist.Address1 = model.Address; // Map to Address1 + exist.Address1 = model.Address; exist.City = model.City; exist.State = model.State; - exist.Zip = model.ZipCode; // Map to Zip - exist.PhoneNumber = model.Phone; // Map to PhoneNumber - exist.Email = model.ContactEmail; // Map to Email + exist.Zip = model.ZipCode; // This is a property, not a method + exist.PhoneNumber = model.Phone; + exist.Email = model.ContactEmail; exist.Status = model.Status; exist.LastModificationTime = DateTime.Now; - // Contact and AccountId don't exist in database - await _db.SaveChangesAsync(); + await _locationDataService.UpdateAsync(exist); return Ok(new DataResponse { Message = "Location Updated Successfully", Status = "200" }); } catch (Exception ex) @@ -166,12 +148,11 @@ 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" }); - _db.Locations.Remove(exist); - await _db.SaveChangesAsync(); + await _locationDataService.DeleteAsync(id); return Ok(new DataResponse { Message = "Location Deleted Successfully", Status = "200" }); } catch (Exception ex) diff --git a/Api.SeaHavenIndustries/Controllers/PMScheduleController.cs b/Api.SeaHavenIndustries/Controllers/PMScheduleController.cs index b6833d3..900f202 100644 --- a/Api.SeaHavenIndustries/Controllers/PMScheduleController.cs +++ b/Api.SeaHavenIndustries/Controllers/PMScheduleController.cs @@ -2,37 +2,36 @@ using Data.SeaHavenIndustries; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using SeaHaven.Services.Interfaces; +using SeaHaven.Services.DTOs; [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 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); } @@ -40,31 +39,42 @@ public class PMScheduleController : ControllerBase [HttpPost("Save")] public async Task Save(PMSchedule_DTO dto) { - PMSchedules model; - - if (dto.Id == 0) + try { - model = new PMSchedules + var userId = User.Identity?.Name ?? "system"; + + 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 (Exception ex) { - model = await _db.PMSchedules.FindAsync(dto.Id); - if (model == null) return NotFound(); - model.LastModificationTime = DateTime.UtcNow; + return BadRequest(new { 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; - - await _db.SaveChangesAsync(); - return Ok(); } } diff --git a/Api.SeaHavenIndustries/Controllers/VendorController.cs b/Api.SeaHavenIndustries/Controllers/VendorController.cs index 3135a2f..0b26f3e 100644 --- a/Api.SeaHavenIndustries/Controllers/VendorController.cs +++ b/Api.SeaHavenIndustries/Controllers/VendorController.cs @@ -3,6 +3,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 { @@ -12,13 +14,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 +30,32 @@ namespace Api.SeaHavenIndustries.Controllers } [HttpGet("GetVendorList")] - public IActionResult GetVendorList(string? search = "", int page = 1, int pageSize = 10) + public async Task 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 GetVendorById(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(); + var vendor = await _vendorService.GetVendorByIdAsync(id); if (vendor == null) return NotFound(new Response { Status = "Error", Message = "Vendor not found" }); @@ -96,21 +68,13 @@ namespace Api.SeaHavenIndustries.Controllers { 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.Identity?.Name ?? "system"; + await _vendorService.CreateVendorAsync(createDto, userId); return Ok(new DataResponse { Message = "Vendor Created", Status = "200" }); } catch (Exception ex) @@ -124,22 +88,13 @@ namespace Api.SeaHavenIndustries.Controllers { try { - var exist = await _db.Vendors.FindAsync(id); - if (exist == null) - return BadRequest(new Response { Status = "Error", Message = "Vendor not found" }); + var updateDto = new UpdateVendorDTO + { + Name = model.CompanyName + }; - 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; - - await _db.SaveChangesAsync(); + var userId = User.Identity?.Name ?? "system"; + await _vendorService.UpdateVendorAsync(id, updateDto, userId); return Ok(new DataResponse { Message = "Vendor Updated", Status = "200" }); } catch (Exception ex) @@ -151,12 +106,8 @@ namespace Api.SeaHavenIndustries.Controllers [HttpDelete("{id}")] public async Task Delete(int id) { - var exist = await _db.Vendors.FindAsync(id); - if (exist == null) - return BadRequest(new Response { Status = "Error", Message = "Vendor not found" }); - - _db.Vendors.Remove(exist); - await _db.SaveChangesAsync(); + var userId = User.Identity?.Name ?? "system"; + await _vendorService.DeleteVendorAsync(id, userId); return Ok(new DataResponse { Message = "Vendor Deleted", Status = "200" }); } diff --git a/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs b/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs index fba8cdd..946d674 100644 --- a/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs +++ b/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs @@ -13,6 +13,7 @@ using System.IO; using System.Net.Http.Headers; using System.Security.Claims; using static System.Runtime.InteropServices.JavaScript.JSType; +using SeaHaven.DataServices.Interfaces; namespace Api.SeaHavenIndustries.Controllers { @@ -23,15 +24,26 @@ namespace Api.SeaHavenIndustries.Controllers public class WorkOrderController : Controller { private readonly UserManager _userManager; - private readonly ApplicationDbContext _db; + private readonly IWorkOrderDataService _workOrderDataService; + private readonly ApplicationDbContext _db; // Keep for complex operations, file handling, dispatch, comments private readonly IWebHostEnvironment _webHostEnvironment; IHttpContextAccessor _httpContext; private readonly Helper.SendMessage _sendMessage; private readonly Helper.VendorPortalTokenService _vendorTokens; private readonly IConfiguration _config; - public WorkOrderController(UserManager userManager, ApplicationDbContext db, IWebHostEnvironment webHostEnvironment, IHttpContextAccessor httpContext, Helper.SendMessage sendMessage, Helper.VendorPortalTokenService vendorTokens, IConfiguration config) + + public WorkOrderController( + UserManager userManager, + IWorkOrderDataService workOrderDataService, + ApplicationDbContext db, + IWebHostEnvironment webHostEnvironment, + IHttpContextAccessor httpContext, + Helper.SendMessage sendMessage, + Helper.VendorPortalTokenService vendorTokens, + IConfiguration config) { _userManager = userManager; + _workOrderDataService = workOrderDataService; _db = db; _webHostEnvironment = webHostEnvironment; _httpContext = httpContext; @@ -373,79 +385,17 @@ namespace Api.SeaHavenIndustries.Controllers // GET: api/WorkOrder/GetWorkOrderList // ----------------------------------- [HttpGet("GetWorkOrderList")] - public IActionResult GetWorkOrderList(string? search = "", int page = 1, int pageSize = 12, string? sortBy = "lastUpdated", string? sortDir = "desc", string? status = "", string? assignee = "", int? locationId = null) + public async Task GetWorkOrderList(string? search = "", int page = 1, int pageSize = 12, string? sortBy = "lastUpdated", string? sortDir = "desc", string? status = "", string? assignee = "", int? locationId = null) { - search ??= ""; - - var query = _db.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 = query.Count(); - - var isAsc = sortDir?.ToLower() == "asc"; - IOrderedQueryable 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 = 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), - _db.WorkOrderAuditLogs.Where(a => a.WorkOrderId == w.Id).Max(a => (DateTime?)a.CreatedAt) - }.Where(d => d.HasValue).Max() - }) - .ToList(); + var (data, totalCount) = await _workOrderDataService.GetWorkOrderListPagedAsync( + page, + pageSize, + search, + status, + assignee, + locationId, + sortBy, + sortDir); var viewModel = new Pagination_DTO { diff --git a/Api.SeaHavenIndustries/DTOs/Contact_DTO.cs b/Api.SeaHavenIndustries/DTOs/Contact_DTO.cs index f2a102b..fc88967 100644 --- a/Api.SeaHavenIndustries/DTOs/Contact_DTO.cs +++ b/Api.SeaHavenIndustries/DTOs/Contact_DTO.cs @@ -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 FromServiceDTOs(IEnumerable serviceDtos) + { + return serviceDtos.Select(FromServiceDTO); + } } } diff --git a/Api.SeaHavenIndustries/DTOs/EditContact_DTO.cs b/Api.SeaHavenIndustries/DTOs/EditContact_DTO.cs index 8ace267..b77f16e 100644 --- a/Api.SeaHavenIndustries/DTOs/EditContact_DTO.cs +++ b/Api.SeaHavenIndustries/DTOs/EditContact_DTO.cs @@ -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; + } } } diff --git a/Api.SeaHavenIndustries/DTOs/EditLocation_DTO.cs b/Api.SeaHavenIndustries/DTOs/EditLocation_DTO.cs index fc17b09..18d1be3 100644 --- a/Api.SeaHavenIndustries/DTOs/EditLocation_DTO.cs +++ b/Api.SeaHavenIndustries/DTOs/EditLocation_DTO.cs @@ -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() + }; + } } } diff --git a/Api.SeaHavenIndustries/DTOs/Location_DTO.cs b/Api.SeaHavenIndustries/DTOs/Location_DTO.cs index e023062..dcd3c03 100644 --- a/Api.SeaHavenIndustries/DTOs/Location_DTO.cs +++ b/Api.SeaHavenIndustries/DTOs/Location_DTO.cs @@ -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 FromServiceDTOs(IEnumerable 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 FromServiceDTOs(IEnumerable serviceDtos) + { + return serviceDtos.Select(FromServiceDTO); + } } } diff --git a/Api.SeaHavenIndustries/Program.cs b/Api.SeaHavenIndustries/Program.cs index f84a1a6..42c0309 100644 --- a/Api.SeaHavenIndustries/Program.cs +++ b/Api.SeaHavenIndustries/Program.cs @@ -60,12 +60,17 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); -// Business Services (Business Logic Layer) - Only register services that exist +// Business Services (Business Logic Layer) builder.Services.AddScoped(); builder.Services.AddScoped(); -// TODO: Add other business services as they are implemented -// - LocationService, EmployeeService, AccountService, VendorService, DispatchService -// - FollowUpService, PMScheduleService, AssetService, CategoryService +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); // ======================================================================== diff --git a/Data.SeaHavenIndustries/Models/Employee.cs b/Data.SeaHavenIndustries/Models/Employee.cs index 5d159be..ed8f753 100644 --- a/Data.SeaHavenIndustries/Models/Employee.cs +++ b/Data.SeaHavenIndustries/Models/Employee.cs @@ -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? 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? 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? Employees { get; set; } diff --git a/SeaHaven.DataServices/Implementation/AccountDataService.cs b/SeaHaven.DataServices/Implementation/AccountDataService.cs index 29f6861..fefc93a 100644 --- a/SeaHaven.DataServices/Implementation/AccountDataService.cs +++ b/SeaHaven.DataServices/Implementation/AccountDataService.cs @@ -32,20 +32,29 @@ namespace SeaHaven.DataServices.Implementation } public async Task<(IEnumerable Items, int TotalCount)> GetPagedAsync( - int page, - int pageSize, + int page, + int pageSize, string? search = null) { - var query = _context.Accounts.AsQueryable(); + search ??= ""; + + var query = _context.Accounts + .Where(a => a.IsDeleted == false || a.IsDeleted == null); if (!string.IsNullOrWhiteSpace(search)) { - query = query.Where(a => a.Name!.Contains(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 - .OrderBy(a => a.Name) + .OrderByDescending(a => a.Id) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); diff --git a/SeaHaven.DataServices/Implementation/AssetDataService.cs b/SeaHaven.DataServices/Implementation/AssetDataService.cs index f5e0dc9..93a3f1e 100644 --- a/SeaHaven.DataServices/Implementation/AssetDataService.cs +++ b/SeaHaven.DataServices/Implementation/AssetDataService.cs @@ -21,6 +21,8 @@ namespace SeaHaven.DataServices.Implementation public async Task GetByIdWithDetailsAsync(int id) { return await _context.Assets + .Include(a => a.Account) + .Include(a => a.Location) .FirstOrDefaultAsync(a => a.Id == id); } @@ -37,15 +39,30 @@ namespace SeaHaven.DataServices.Implementation } public async Task<(IEnumerable Items, int TotalCount)> GetPagedAsync( - int page, - int pageSize, + int page, + int pageSize, string? search = null) { - var query = _context.Assets.AsQueryable(); + 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 - .OrderBy(a => a.Id) + .OrderByDescending(a => a.Id) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); diff --git a/SeaHaven.DataServices/Implementation/ContactDataService.cs b/SeaHaven.DataServices/Implementation/ContactDataService.cs index 5cb50eb..8196d2b 100644 --- a/SeaHaven.DataServices/Implementation/ContactDataService.cs +++ b/SeaHaven.DataServices/Implementation/ContactDataService.cs @@ -65,18 +65,22 @@ namespace SeaHaven.DataServices.Implementation } public async Task<(IEnumerable Items, int TotalCount)> GetPagedAsync( - int page, - int pageSize, - string? search = null, + int page, + int pageSize, + string? search = null, string? type = null) { + search ??= ""; var query = _context.Contacts.AsQueryable(); if (!string.IsNullOrWhiteSpace(search)) { - query = query.Where(c => - (c.FirstName + " " + c.LastName).Contains(search) || - c.Email!.Contains(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)) @@ -86,7 +90,7 @@ namespace SeaHaven.DataServices.Implementation var totalCount = await query.CountAsync(); var items = await query - .OrderBy(c => c.LastName) + .OrderByDescending(c => c.Id) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); diff --git a/SeaHaven.DataServices/Implementation/EmployeeDataService.cs b/SeaHaven.DataServices/Implementation/EmployeeDataService.cs index aadcd48..4606d11 100644 --- a/SeaHaven.DataServices/Implementation/EmployeeDataService.cs +++ b/SeaHaven.DataServices/Implementation/EmployeeDataService.cs @@ -23,7 +23,9 @@ namespace SeaHaven.DataServices.Implementation 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); } @@ -33,20 +35,34 @@ namespace SeaHaven.DataServices.Implementation } public async Task<(IEnumerable Items, int TotalCount)> GetPagedAsync( - int page, - int pageSize, + int page, + int pageSize, string? search = null) { - var query = _context.Employees.AsQueryable(); + 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)) { - query = query.Where(e => (e.FirstName + " " + e.LastName).Contains(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 - .OrderBy(e => e.LastName) + .OrderByDescending(e => e.Id) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); diff --git a/SeaHaven.DataServices/Implementation/FollowUpDataService.cs b/SeaHaven.DataServices/Implementation/FollowUpDataService.cs index f07f3c5..4f34528 100644 --- a/SeaHaven.DataServices/Implementation/FollowUpDataService.cs +++ b/SeaHaven.DataServices/Implementation/FollowUpDataService.cs @@ -21,6 +21,10 @@ namespace SeaHaven.DataServices.Implementation public async Task 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); } @@ -44,21 +48,37 @@ namespace SeaHaven.DataServices.Implementation } public async Task<(IEnumerable Items, int TotalCount)> GetPagedAsync( - int page, - int pageSize, - string? search = null, + int page, + int pageSize, + string? search = null, string? status = null) { - var query = _context.FollowUps.AsQueryable(); + search ??= ""; + status = string.IsNullOrWhiteSpace(status) ? "Open" : status; - if (!string.IsNullOrWhiteSpace(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)) { - query = query.Where(f => f.Status == status); + 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.CreatedDate) + .OrderByDescending(f => f.Id) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); diff --git a/SeaHaven.DataServices/Implementation/LocationDataService.cs b/SeaHaven.DataServices/Implementation/LocationDataService.cs index 43badbb..65c46df 100644 --- a/SeaHaven.DataServices/Implementation/LocationDataService.cs +++ b/SeaHaven.DataServices/Implementation/LocationDataService.cs @@ -94,5 +94,39 @@ namespace SeaHaven.DataServices.Implementation { return await _context.Locations.CountAsync(); } + + public async Task<(IEnumerable 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); + } } } diff --git a/SeaHaven.DataServices/Implementation/PMScheduleDataService.cs b/SeaHaven.DataServices/Implementation/PMScheduleDataService.cs index 90b3d27..a2418ea 100644 --- a/SeaHaven.DataServices/Implementation/PMScheduleDataService.cs +++ b/SeaHaven.DataServices/Implementation/PMScheduleDataService.cs @@ -29,6 +29,15 @@ namespace SeaHaven.DataServices.Implementation return await _context.PMSchedules.ToListAsync(); } + public async Task> GetAllWithDetailsAsync() + { + return await _context.PMSchedules + .Include(x => x.Asset) + .Include(x => x.Location) + .OrderByDescending(x => x.Id) + .ToListAsync(); + } + public async Task> GetByLocationIdAsync(int locationId) { return await _context.PMSchedules diff --git a/SeaHaven.DataServices/Implementation/VendorDataService.cs b/SeaHaven.DataServices/Implementation/VendorDataService.cs index 416ad31..6eb2eeb 100644 --- a/SeaHaven.DataServices/Implementation/VendorDataService.cs +++ b/SeaHaven.DataServices/Implementation/VendorDataService.cs @@ -30,15 +30,27 @@ namespace SeaHaven.DataServices.Implementation } public async Task<(IEnumerable Items, int TotalCount)> GetPagedAsync( - int page, - int pageSize, + int page, + int pageSize, string? search = null) { - var query = _context.Vendors.AsQueryable(); + 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 - .OrderBy(v => v.Id) + .OrderByDescending(v => v.Id) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); diff --git a/SeaHaven.DataServices/Implementation/WorkOrderDataService.cs b/SeaHaven.DataServices/Implementation/WorkOrderDataService.cs index 1f6667e..d1107bd 100644 --- a/SeaHaven.DataServices/Implementation/WorkOrderDataService.cs +++ b/SeaHaven.DataServices/Implementation/WorkOrderDataService.cs @@ -66,18 +66,18 @@ namespace SeaHaven.DataServices.Implementation } public async Task<(IEnumerable Items, int TotalCount)> GetPagedAsync( - int page, - int pageSize, - string? search = null, - string? status = null, + 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) || + query = query.Where(w => + w.WorkerOrderNumber!.Contains(search) || w.WorkerOrderTitle!.Contains(search)); } @@ -101,6 +101,91 @@ namespace SeaHaven.DataServices.Implementation return (items, totalCount); } + public async Task<(IEnumerable 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 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 AddAsync(WorkOrder workOrder) { workOrder.CreatedDate = DateTime.Now; diff --git a/SeaHaven.DataServices/Interfaces/ILocationDataService.cs b/SeaHaven.DataServices/Interfaces/ILocationDataService.cs index 6f4d90a..2865e42 100644 --- a/SeaHaven.DataServices/Interfaces/ILocationDataService.cs +++ b/SeaHaven.DataServices/Interfaces/ILocationDataService.cs @@ -8,7 +8,13 @@ namespace SeaHaven.DataServices.Interfaces Task GetByIdWithDetailsAsync(int id); Task> GetAllAsync(); Task> GetByAccountIdAsync(int accountId); + + // Paged results returning DTOs Task<(IEnumerable Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null); + + // For addressbook endpoint - returns simplified DTO + Task<(IEnumerable Items, int TotalCount)> GetAddressbookPagedAsync(int page, int pageSize, string? search = null); + Task AddAsync(Locations location); Task UpdateAsync(Locations location); Task DeleteAsync(int id); diff --git a/SeaHaven.DataServices/Interfaces/IPMScheduleDataService.cs b/SeaHaven.DataServices/Interfaces/IPMScheduleDataService.cs index f655777..e205093 100644 --- a/SeaHaven.DataServices/Interfaces/IPMScheduleDataService.cs +++ b/SeaHaven.DataServices/Interfaces/IPMScheduleDataService.cs @@ -7,6 +7,7 @@ namespace SeaHaven.DataServices.Interfaces Task GetByIdAsync(int id); Task GetByIdWithDetailsAsync(int id); Task> GetAllAsync(); + Task> GetAllWithDetailsAsync(); Task> GetByLocationIdAsync(int locationId); Task<(IEnumerable Items, int TotalCount)> GetPagedAsync(int page, int pageSize, string? search = null); Task AddAsync(PMSchedules pmSchedule); diff --git a/SeaHaven.DataServices/Interfaces/IWorkOrderDataService.cs b/SeaHaven.DataServices/Interfaces/IWorkOrderDataService.cs index 1e70a27..014c294 100644 --- a/SeaHaven.DataServices/Interfaces/IWorkOrderDataService.cs +++ b/SeaHaven.DataServices/Interfaces/IWorkOrderDataService.cs @@ -16,11 +16,22 @@ namespace SeaHaven.DataServices.Interfaces // Paged results Task<(IEnumerable Items, int TotalCount)> GetPagedAsync( - int page, - int pageSize, - string? search = null, + int page, + int pageSize, + string? search = null, string? status = null, string? priority = null); + + // Paged list with detailed information (for GetWorkOrderList endpoint) + Task<(IEnumerable 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 AddAsync(WorkOrder workOrder); diff --git a/SeaHaven.Services/DTOs/AccountDTOs.cs b/SeaHaven.Services/DTOs/AccountDTOs.cs index 662a9ee..aa51689 100644 --- a/SeaHaven.Services/DTOs/AccountDTOs.cs +++ b/SeaHaven.Services/DTOs/AccountDTOs.cs @@ -4,6 +4,7 @@ namespace SeaHaven.Services.DTOs { 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; } diff --git a/SeaHaven.Services/DTOs/AssetDTOs.cs b/SeaHaven.Services/DTOs/AssetDTOs.cs index cd8eb76..8c5c540 100644 --- a/SeaHaven.Services/DTOs/AssetDTOs.cs +++ b/SeaHaven.Services/DTOs/AssetDTOs.cs @@ -4,8 +4,6 @@ namespace SeaHaven.Services.DTOs { public int Id { get; set; } public string? Name { get; set; } - public string? Type { get; set; } - public string? SerialNumber { get; set; } public int? LocationId { get; set; } public string? LocationName { get; set; } public DateTime? CreatedDate { get; set; } @@ -14,16 +12,12 @@ namespace SeaHaven.Services.DTOs public class CreateAssetDTO { public required string Name { get; set; } - public string? Type { get; set; } - public string? SerialNumber { get; set; } public int? LocationId { get; set; } } public class UpdateAssetDTO { public string? Name { get; set; } - public string? Type { get; set; } - public string? SerialNumber { get; set; } public int? LocationId { get; set; } } } diff --git a/SeaHaven.Services/DTOs/FollowUpDTOs.cs b/SeaHaven.Services/DTOs/FollowUpDTOs.cs index 23668a6..4200416 100644 --- a/SeaHaven.Services/DTOs/FollowUpDTOs.cs +++ b/SeaHaven.Services/DTOs/FollowUpDTOs.cs @@ -3,32 +3,41 @@ namespace SeaHaven.Services.DTOs public class FollowUpDTO { public int Id { get; set; } - public string? Subject { get; set; } - public string? Description { 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 string? AssignedTo { get; set; } - public DateTime? DueDate { 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 string? CreatedBy { get; set; } } public class CreateFollowUpDTO { - public required string Subject { get; set; } - public string? Description { get; set; } - public string? Status { get; set; } + 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 string? AssignedTo { get; set; } - public DateTime? DueDate { get; set; } + public int? AccountId { get; set; } + public int? LocationId { get; set; } } public class UpdateFollowUpDTO { - public string? Subject { get; set; } - public string? Description { get; set; } + public string? Reason { get; set; } + public DateTime? ScheduleDate { get; set; } + public TimeSpan? ScheduleStartTime { get; set; } public string? Status { get; set; } - public string? AssignedTo { get; set; } - public DateTime? DueDate { get; set; } + public int? EmployeeId { get; set; } + public int? WorkOrderId { get; set; } + public int? AccountId { get; set; } + public int? LocationId { get; set; } } } diff --git a/SeaHaven.Services/DTOs/LocationDTOs.cs b/SeaHaven.Services/DTOs/LocationDTOs.cs index 2e33eb7..09e41b6 100644 --- a/SeaHaven.Services/DTOs/LocationDTOs.cs +++ b/SeaHaven.Services/DTOs/LocationDTOs.cs @@ -3,13 +3,16 @@ namespace SeaHaven.Services.DTOs public class LocationDTO { public int Id { get; set; } - public string? LocationName { get; set; } - public string? Address { 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? Zipcode { get; set; } - public int? AccountId { get; set; } - public string? AccountName { 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; } } diff --git a/SeaHaven.Services/DTOs/PMScheduleDTOs.cs b/SeaHaven.Services/DTOs/PMScheduleDTOs.cs index 7c0daf5..57a5fa9 100644 --- a/SeaHaven.Services/DTOs/PMScheduleDTOs.cs +++ b/SeaHaven.Services/DTOs/PMScheduleDTOs.cs @@ -4,29 +4,33 @@ namespace SeaHaven.Services.DTOs { public int Id { get; set; } public string? Name { get; set; } - public string? Description { 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 string? Frequency { get; set; } - public DateTime? NextDueDate { get; set; } public DateTime? CreatedDate { get; set; } } public class CreatePMScheduleDTO { public required string Name { get; set; } - public string? Description { 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 string? Frequency { get; set; } - public DateTime? NextDueDate { get; set; } } public class UpdatePMScheduleDTO { public string? Name { get; set; } - public string? Description { get; set; } - public int? LocationId { get; set; } public string? Frequency { get; set; } - public DateTime? NextDueDate { get; set; } + public DateTime? StartDate { get; set; } + public bool? IsActive { get; set; } + public int? AssetId { get; set; } + public int? LocationId { get; set; } } } diff --git a/SeaHaven.Services/Implementation/AccountService.cs b/SeaHaven.Services/Implementation/AccountService.cs new file mode 100644 index 0000000..b059ea7 --- /dev/null +++ b/SeaHaven.Services/Implementation/AccountService.cs @@ -0,0 +1,124 @@ +using Data.SeaHavenIndustries; +using SeaHaven.DataServices.Interfaces; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; + +namespace SeaHaven.Services.Implementation +{ + /// + /// Account service implementation + /// Contains business logic, validation, and orchestration for Account operations + /// + public class AccountService : IAccountService + { + private readonly IAccountDataService _accountDataService; + + public AccountService(IAccountDataService accountDataService) + { + _accountDataService = accountDataService; + } + + public async Task GetAccountByIdAsync(int id) + { + var account = await _accountDataService.GetByIdAsync(id); + return account == null ? null : MapToDTO(account); + } + + public async Task GetAccountByIdWithDetailsAsync(int id) + { + var account = await _accountDataService.GetByIdWithDetailsAsync(id); + return account == null ? null : MapToDTO(account); + } + + public async Task> GetAllAccountsAsync() + { + var accounts = await _accountDataService.GetAllAsync(); + return accounts.Select(MapToDTO); + } + + public async Task> GetAccountsPagedAsync(int page, int pageSize, string? search = null) + { + var (items, totalCount) = await _accountDataService.GetPagedAsync(page, pageSize, search); + return new PagedResult + { + Items = items.Select(MapToDTO).ToList(), + TotalCount = totalCount, + Page = page, + PageSize = pageSize + }; + } + + public async Task CreateAccountAsync(CreateAccountDTO dto, string userId) + { + 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 UpdateAccountAsync(int id, UpdateAccountDTO dto, string userId) + { + 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 AccountExistsAsync(int id) + { + return await _accountDataService.ExistsAsync(id); + } + + public async Task 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 + }; + } + } +} diff --git a/SeaHaven.Services/Implementation/AssetService.cs b/SeaHaven.Services/Implementation/AssetService.cs new file mode 100644 index 0000000..d803227 --- /dev/null +++ b/SeaHaven.Services/Implementation/AssetService.cs @@ -0,0 +1,123 @@ +using Data.SeaHavenIndustries; +using SeaHaven.DataServices.Interfaces; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; + +namespace SeaHaven.Services.Implementation +{ + /// + /// Asset service implementation + /// Contains business logic, validation, and orchestration for Asset operations + /// + public class AssetService : IAssetService + { + private readonly IAssetDataService _assetDataService; + + public AssetService(IAssetDataService assetDataService) + { + _assetDataService = assetDataService; + } + + public async Task GetAssetByIdAsync(int id) + { + var asset = await _assetDataService.GetByIdAsync(id); + return asset == null ? null : MapToDTO(asset); + } + + public async Task GetAssetByIdWithDetailsAsync(int id) + { + var asset = await _assetDataService.GetByIdWithDetailsAsync(id); + return asset == null ? null : MapToDTO(asset); + } + + public async Task> GetAllAssetsAsync() + { + var assets = await _assetDataService.GetAllAsync(); + return assets.Select(MapToDTO); + } + + public async Task> GetAssetsByLocationIdAsync(int locationId) + { + var assets = await _assetDataService.GetAllAsync(); + return assets.Where(a => a.LocationId == locationId).Select(MapToDTO); + } + + public async Task> GetAssetsPagedAsync(int page, int pageSize, string? search = null) + { + var (items, totalCount) = await _assetDataService.GetPagedAsync(page, pageSize, search); + return new PagedResult + { + Items = items.Select(MapToDTO).ToList(), + TotalCount = totalCount, + Page = page, + PageSize = pageSize + }; + } + + public async Task CreateAssetAsync(CreateAssetDTO dto, string userId) + { + 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 UpdateAssetAsync(int id, UpdateAssetDTO dto, string userId) + { + 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 AssetExistsAsync(int id) + { + return await _assetDataService.ExistsAsync(id); + } + + public async Task 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 + }; + } + } +} diff --git a/SeaHaven.Services/Implementation/DispatchService.cs b/SeaHaven.Services/Implementation/DispatchService.cs new file mode 100644 index 0000000..817a21f --- /dev/null +++ b/SeaHaven.Services/Implementation/DispatchService.cs @@ -0,0 +1,139 @@ +using Data.SeaHavenIndustries; +using SeaHaven.DataServices.Interfaces; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; + +namespace SeaHaven.Services.Implementation +{ + /// + /// Dispatch service implementation + /// Contains business logic, validation, and orchestration for Dispatch operations + /// + public class DispatchService : IDispatchService + { + private readonly IDispatchDataService _dispatchDataService; + + public DispatchService(IDispatchDataService dispatchDataService) + { + _dispatchDataService = dispatchDataService; + } + + public async Task GetDispatchByIdAsync(int id) + { + var dispatch = await _dispatchDataService.GetByIdAsync(id); + return dispatch == null ? null : MapToDTO(dispatch); + } + + public async Task GetDispatchByIdWithDetailsAsync(int id) + { + var dispatch = await _dispatchDataService.GetByIdWithDetailsAsync(id); + return dispatch == null ? null : MapToDTO(dispatch); + } + + public async Task> GetAllDispatchesAsync() + { + var dispatches = await _dispatchDataService.GetAllAsync(); + return dispatches.Select(MapToDTO); + } + + public async Task> GetDispatchesByVendorIdAsync(int vendorId) + { + var dispatches = await _dispatchDataService.GetByVendorIdAsync(vendorId); + return dispatches.Select(MapToDTO); + } + + public async Task> GetDispatchesPagedAsync(int page, int pageSize, string? search = null, string? status = null) + { + var (items, totalCount) = await _dispatchDataService.GetPagedAsync(page, pageSize, search, status); + return new PagedResult + { + Items = items.Select(MapToDTO).ToList(), + TotalCount = totalCount, + Page = page, + PageSize = pageSize + }; + } + + public async Task CreateDispatchAsync(CreateDispatchDTO dto, string userId) + { + 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 UpdateDispatchAsync(int id, UpdateDispatchDTO dto, string userId) + { + 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 DispatchExistsAsync(int id) + { + return await _dispatchDataService.ExistsAsync(id); + } + + public async Task GenerateNextDispatchNumberAsync() + { + var count = await _dispatchDataService.CountAsync(); + return $"DISP-{(count + 1):D6}"; + } + + public async Task 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 + }; + } + } +} diff --git a/SeaHaven.Services/Implementation/EmployeeService.cs b/SeaHaven.Services/Implementation/EmployeeService.cs new file mode 100644 index 0000000..099c4a2 --- /dev/null +++ b/SeaHaven.Services/Implementation/EmployeeService.cs @@ -0,0 +1,149 @@ +using Data.SeaHavenIndustries; +using SeaHaven.DataServices.Interfaces; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; + +namespace SeaHaven.Services.Implementation +{ + /// + /// Employee service implementation + /// Contains business logic, validation, and orchestration for Employee operations + /// + public class EmployeeService : IEmployeeService + { + private readonly IEmployeeDataService _employeeDataService; + + public EmployeeService(IEmployeeDataService employeeDataService) + { + _employeeDataService = employeeDataService; + } + + // Query operations + public async Task GetEmployeeByIdAsync(int id) + { + var employee = await _employeeDataService.GetByIdAsync(id); + return employee == null ? null : MapToDTO(employee); + } + + public async Task GetEmployeeByIdWithDetailsAsync(int id) + { + var employee = await _employeeDataService.GetByIdWithDetailsAsync(id); + return employee == null ? null : MapToDTO(employee); + } + + public async Task> GetAllEmployeesAsync() + { + var employees = await _employeeDataService.GetAllAsync(); + return employees.Select(MapToDTO); + } + + public async Task> GetEmployeesPagedAsync(int page, int pageSize, string? search = null) + { + var (items, totalCount) = await _employeeDataService.GetPagedAsync(page, pageSize, search); + return new PagedResult + { + Items = items.Select(MapToDTO).ToList(), + TotalCount = totalCount, + Page = page, + PageSize = pageSize + }; + } + + public async Task CreateEmployeeAsync(CreateEmployeeDTO dto, string userId) + { + 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 UpdateEmployeeAsync(int id, UpdateEmployeeDTO dto, string userId) + { + 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 EmployeeExistsAsync(int id) + { + return await _employeeDataService.ExistsAsync(id); + } + + public async Task 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 + }; + } + } +} diff --git a/SeaHaven.Services/Implementation/FollowUpService.cs b/SeaHaven.Services/Implementation/FollowUpService.cs new file mode 100644 index 0000000..9dce139 --- /dev/null +++ b/SeaHaven.Services/Implementation/FollowUpService.cs @@ -0,0 +1,150 @@ +using Data.SeaHavenIndustries; +using SeaHaven.DataServices.Interfaces; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; + +namespace SeaHaven.Services.Implementation +{ + /// + /// Follow-Up service implementation + /// Contains business logic, validation, and orchestration for Follow-Up operations + /// + public class FollowUpService : IFollowUpService + { + private readonly IFollowUpDataService _followUpDataService; + + public FollowUpService(IFollowUpDataService followUpDataService) + { + _followUpDataService = followUpDataService; + } + + public async Task GetFollowUpByIdAsync(int id) + { + var followUp = await _followUpDataService.GetByIdAsync(id); + return followUp == null ? null : MapToDTO(followUp); + } + + public async Task GetFollowUpByIdWithDetailsAsync(int id) + { + var followUp = await _followUpDataService.GetByIdWithDetailsAsync(id); + return followUp == null ? null : MapToDTO(followUp); + } + + public async Task> GetAllFollowUpsAsync() + { + var followUps = await _followUpDataService.GetAllAsync(); + return followUps.Select(MapToDTO); + } + + public async Task> GetFollowUpsByWorkOrderIdAsync(int workOrderId) + { + var followUps = await _followUpDataService.GetByWorkOrderIdAsync(workOrderId); + return followUps.Select(MapToDTO); + } + + public async Task> 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 + { + Items = filteredItems.Select(MapToDTO).ToList(), + TotalCount = totalCount, + Page = page, + PageSize = pageSize + }; + } + + public async Task CreateFollowUpAsync(CreateFollowUpDTO dto, string userId) + { + 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 UpdateFollowUpAsync(int id, UpdateFollowUpDTO dto, string userId) + { + 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 FollowUpExistsAsync(int id) + { + return await _followUpDataService.ExistsAsync(id); + } + + public async Task 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 + }; + } + } +} diff --git a/SeaHaven.Services/Implementation/LocationService.cs b/SeaHaven.Services/Implementation/LocationService.cs new file mode 100644 index 0000000..67813f9 --- /dev/null +++ b/SeaHaven.Services/Implementation/LocationService.cs @@ -0,0 +1,142 @@ +using Data.SeaHavenIndustries; +using SeaHaven.DataServices.Interfaces; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; + +namespace SeaHaven.Services.Implementation +{ + /// + /// Location service implementation + /// Contains business logic, validation, and orchestration for Location operations + /// + public class LocationService : ILocationService + { + private readonly ILocationDataService _locationDataService; + + public LocationService(ILocationDataService locationDataService) + { + _locationDataService = locationDataService; + } + + // Query operations + public async Task GetLocationByIdAsync(int id) + { + var location = await _locationDataService.GetByIdAsync(id); + return location == null ? null : MapToDTO(location); + } + + public async Task GetLocationByIdWithDetailsAsync(int id) + { + var location = await _locationDataService.GetByIdWithDetailsAsync(id); + return location == null ? null : MapToDTO(location); + } + + public async Task> GetAllLocationsAsync() + { + var locations = await _locationDataService.GetAllAsync(); + return locations.Select(MapToDTO); + } + + public async Task> GetLocationsByAccountIdAsync(int accountId) + { + var locations = await _locationDataService.GetByAccountIdAsync(accountId); + return locations.Select(MapToDTO); + } + + public async Task> GetLocationsPagedAsync(int page, int pageSize, string? search = null) + { + var (items, totalCount) = await _locationDataService.GetPagedAsync(page, pageSize, search); + return new PagedResult + { + Items = items.Select(MapToDTO).ToList(), + TotalCount = totalCount, + Page = page, + PageSize = pageSize + }; + } + + public async Task CreateLocationAsync(CreateLocationDTO dto, string userId) + { + 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 UpdateLocationAsync(int id, UpdateLocationDTO dto, string userId) + { + 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 LocationExistsAsync(int id) + { + return await _locationDataService.ExistsAsync(id); + } + + public async Task 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 + }; + } + } +} diff --git a/SeaHaven.Services/Implementation/PMScheduleService.cs b/SeaHaven.Services/Implementation/PMScheduleService.cs new file mode 100644 index 0000000..ab12d1e --- /dev/null +++ b/SeaHaven.Services/Implementation/PMScheduleService.cs @@ -0,0 +1,136 @@ +using Data.SeaHavenIndustries; +using SeaHaven.DataServices.Interfaces; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; + +namespace SeaHaven.Services.Implementation +{ + /// + /// PM Schedule service implementation + /// Contains business logic, validation, and orchestration for PM Schedule operations + /// + public class PMScheduleService : IPMScheduleService + { + private readonly IPMScheduleDataService _pmScheduleDataService; + + public PMScheduleService(IPMScheduleDataService pmScheduleDataService) + { + _pmScheduleDataService = pmScheduleDataService; + } + + public async Task GetPMScheduleByIdAsync(int id) + { + var pmSchedule = await _pmScheduleDataService.GetByIdAsync(id); + return pmSchedule == null ? null : MapToDTO(pmSchedule); + } + + public async Task GetPMScheduleByIdWithDetailsAsync(int id) + { + var pmSchedule = await _pmScheduleDataService.GetByIdWithDetailsAsync(id); + return pmSchedule == null ? null : MapToDTO(pmSchedule); + } + + public async Task> GetAllPMSchedulesAsync() + { + var pmSchedules = await _pmScheduleDataService.GetAllWithDetailsAsync(); + return pmSchedules.Select(MapToDTO); + } + + public async Task> GetPMSchedulesByLocationIdAsync(int locationId) + { + var pmSchedules = await _pmScheduleDataService.GetAllWithDetailsAsync(); + return pmSchedules.Where(p => p.LocationId == locationId).Select(MapToDTO); + } + + public async Task> GetPMSchedulesPagedAsync(int page, int pageSize, string? search = null) + { + var (items, totalCount) = await _pmScheduleDataService.GetPagedAsync(page, pageSize, search); + return new PagedResult + { + Items = items.Select(MapToDTO).ToList(), + TotalCount = totalCount, + Page = page, + PageSize = pageSize + }; + } + + public async Task CreatePMScheduleAsync(CreatePMScheduleDTO dto, string userId) + { + 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 UpdatePMScheduleAsync(int id, UpdatePMScheduleDTO dto, string userId) + { + 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 PMScheduleExistsAsync(int id) + { + return await _pmScheduleDataService.ExistsAsync(id); + } + + public async Task 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 + }; + } + } +} diff --git a/SeaHaven.Services/Implementation/VendorService.cs b/SeaHaven.Services/Implementation/VendorService.cs new file mode 100644 index 0000000..8bbe97c --- /dev/null +++ b/SeaHaven.Services/Implementation/VendorService.cs @@ -0,0 +1,130 @@ +using Data.SeaHavenIndustries; +using SeaHaven.DataServices.Interfaces; +using SeaHaven.Services.DTOs; +using SeaHaven.Services.Interfaces; + +namespace SeaHaven.Services.Implementation +{ + /// + /// Vendor service implementation + /// Contains business logic, validation, and orchestration for Vendor operations + /// + public class VendorService : IVendorService + { + private readonly IVendorDataService _vendorDataService; + + public VendorService(IVendorDataService vendorDataService) + { + _vendorDataService = vendorDataService; + } + + public async Task GetVendorByIdAsync(int id) + { + var vendor = await _vendorDataService.GetByIdAsync(id); + return vendor == null ? null : MapToDTO(vendor); + } + + public async Task GetVendorByIdWithDetailsAsync(int id) + { + var vendor = await _vendorDataService.GetByIdWithDetailsAsync(id); + return vendor == null ? null : MapToDTO(vendor); + } + + public async Task> GetAllVendorsAsync() + { + var vendors = await _vendorDataService.GetAllAsync(); + return vendors.Select(MapToDTO); + } + + public async Task> GetVendorsPagedAsync(int page, int pageSize, string? search = null) + { + var (items, totalCount) = await _vendorDataService.GetPagedAsync(page, pageSize, search); + return new PagedResult + { + Items = items.Select(MapToDTO).ToList(), + TotalCount = totalCount, + Page = page, + PageSize = pageSize + }; + } + + public async Task CreateVendorAsync(CreateVendorDTO dto, string userId) + { + 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 UpdateVendorAsync(int id, UpdateVendorDTO dto, string userId) + { + 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 VendorExistsAsync(int id) + { + return await _vendorDataService.ExistsAsync(id); + } + + public async Task 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 + }; + } + } +}