diff --git a/ARCHITECTURE_AT_A_GLANCE.md b/ARCHITECTURE_AT_A_GLANCE.md
new file mode 100644
index 0000000..635b2d5
--- /dev/null
+++ b/ARCHITECTURE_AT_A_GLANCE.md
@@ -0,0 +1,344 @@
+# Architecture At A Glance
+
+## π The 4-Layer Architecture
+
+```
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β π API LAYER (Controllers) β
+β - HTTP Endpoints β
+β - Authentication β
+β - Exception β HTTP Status Code mapping β
+β β NO Business Logic β
+β β NO Database Access β
+βββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
+ β
+ β Calls
+ βΌ
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β πΌ SERVICE LAYER (Business Logic) β
+β - FluentValidation β
+β - Business Rules β
+β - Orchestration β
+β - DTO Transformations β
+β β NO HTTP Concerns β
+β β NO Database Access β
+βββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
+ β
+ β Calls
+ βΌ
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β πΎ DATA SERVICE LAYER (Persistence) β
+β - CRUD Operations β
+β - Database Queries β
+β - Audit Field Management β
+β β NO Validation β
+β β NO Business Logic β
+βββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
+ β
+ β Uses
+ βΌ
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β ποΈ DATA LAYER (EF Core + Entities) β
+β - DbContext β
+β - Entity Models (POCOs) β
+β - Migrations β
+ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+```
+
+---
+
+## π― Responsibilities Matrix
+
+| Layer | What It Does | What It Doesn't Do |
+|-------|--------------|-------------------|
+| **Controller** | β’ Handle HTTP requests
β’ Authenticate users
β’ Map API DTOs
β’ Catch exceptions
β’ Return HTTP status codes | β’ Business logic
β’ Validation rules
β’ Database queries |
+| **Service** | β’ Validate data (FluentValidation)
β’ Apply business rules
β’ Coordinate operations
β’ Transform data | β’ HTTP concerns
β’ Database operations |
+| **DataService** | β’ Execute database queries
β’ CRUD operations
β’ Set audit fields | β’ Validation
β’ Business rules |
+| **Data** | β’ Define entities
β’ Database schema | β’ Application logic |
+
+---
+
+## π Request Flow
+
+### Creating a Contact (Example)
+
+```
+1. HTTP POST /api/Contact/Create
+ β
+2. ContactController.Create()
+ - Checks authentication
+ - Gets userId from claims
+ β
+3. ContactService.CreateContactAsync()
+ - Validates using FluentValidation
+ - Applies business rules
+ β
+4. ContactDataService.CreateAsync()
+ - Creates entity
+ - Sets audit fields
+ - Saves to database
+ β
+5. Returns through layers:
+ Entity β DataDTO β ServiceDTO β ApiDTO β HTTP Response
+```
+
+---
+
+## π¦ DTO Flow
+
+```
+Frontend (JSON)
+ β
+API DTO (Contact_DTO) β Controller receives this
+ β
+Service DTO (CreateContactDTO) β Service validates this
+ β
+Data DTO (CreateContactDataDTO) β DataService uses this
+ β
+Entity (Contact) β Database stores this
+```
+
+---
+
+## π Dependency Injection
+
+### Automatic Assembly Scanning
+
+Services auto-register via assembly scanning (no manual registration needed).
+
+```csharp
+// Program.cs
+builder.Services.AddDataServices(); // Auto-register data services
+builder.Services.AddBusinessServices(); // Auto-register business services + validators
+```
+
+### Naming Convention (REQUIRED)
+
+| Interface | Implementation | Registered? |
+|-----------|---------------|-------------|
+| `IContactService` | `ContactService` | β
YES |
+| `ISomething` | `SomethingElse` | β NO |
+
+**Rule**: Interface = `I` + Class name exactly
+
+---
+
+## β
Validation
+
+### Where and How
+
+```
+β Controller β Catches ValidationException
+β
Service β Runs FluentValidation.Validate()
+β DataService β No validation
+```
+
+### FluentValidation Example
+
+```csharp
+public class CreateContactDTOValidator : AbstractValidator
+{
+ public CreateContactDTOValidator()
+ {
+ RuleFor(x => x.FirstName)
+ .NotEmpty().WithMessage("First name is required")
+ .MaximumLength(50);
+
+ RuleFor(x => x.Email)
+ .EmailAddress().WithMessage("Invalid email");
+ }
+}
+```
+
+### In Service
+
+```csharp
+var validationResult = await _validator.ValidateAsync(dto);
+if (!validationResult.IsValid)
+{
+ throw new ValidationException(validationResult.Errors);
+}
+```
+
+---
+
+## π¨ Exception Handling
+
+### Standard Pattern (All Controllers)
+
+```csharp
+try
+{
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(...);
+
+ await _service.SomeMethod(dto, userId);
+ return Ok(...);
+}
+catch (ValidationException vex) // 400 Bad Request
+{
+ return BadRequest(...);
+}
+catch (KeyNotFoundException) // 404 Not Found
+{
+ return NotFound(...);
+}
+catch (Exception ex) // 500 Internal Server Error
+{
+ return StatusCode(500, ...);
+}
+```
+
+---
+
+## π Authentication
+
+### Getting User ID
+
+```csharp
+// Method 1: Claims (Preferred)
+var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+
+// Method 2: UserManager (for Identity)
+var userId = _userManager.GetUserId(User);
+
+// Always check for null
+if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+```
+
+### Audit Fields
+
+Every entity has:
+```csharp
+public string CreatedBy { get; set; } // User ID (GUID)
+public DateTime CreatedDate { get; set; } // UTC timestamp
+public string ModifiedBy { get; set; } // User ID (GUID)
+public DateTime ModifiedDate { get; set; } // UTC timestamp
+```
+
+Set in DataService:
+```csharp
+CreatedBy = userId,
+CreatedDate = DateTime.UtcNow,
+ModifiedBy = userId,
+ModifiedDate = DateTime.UtcNow
+```
+
+---
+
+## π Naming Conventions
+
+| Component | Pattern | Example |
+|-----------|---------|---------|
+| Entity | `[Entity]` | `Contact` |
+| Controller | `[Entity]Controller` | `ContactController` |
+| Service Interface | `I[Entity]Service` | `IContactService` |
+| Service Class | `[Entity]Service` | `ContactService` |
+| DataService Interface | `I[Entity]DataService` | `IContactDataService` |
+| DataService Class | `[Entity]DataService` | `ContactDataService` |
+| API DTO | `[Entity]_DTO` | `Contact_DTO` |
+| Service DTO | `[Action][Entity]DTO` | `CreateContactDTO` |
+| Data DTO | `[Action][Entity]DataDTO` | `CreateContactDataDTO` |
+| Validator | `[DTO]Validator` | `CreateContactDTOValidator` |
+
+---
+
+## π¨ Code Templates
+
+### Controller Method Template
+```csharp
+[HttpPost("ActionName")]
+public async Task ActionName([FromBody] SomeDTO dto)
+{
+ try
+ {
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
+ await _service.Method(dto, userId);
+
+ return Ok(new Response { Status = "Success", Message = "Success message" });
+ }
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
+ }
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "Not found" });
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
+ }
+}
+```
+
+### Service Method Template
+```csharp
+public async Task MethodAsync(InputDTO dto, string userId)
+{
+ // 1. Validate
+ var validationResult = await _validator.ValidateAsync(dto);
+ if (!validationResult.IsValid)
+ {
+ throw new ValidationException(validationResult.Errors);
+ }
+
+ // 2. Business logic (if any)
+
+ // 3. Call DataService
+ var result = await _dataService.MethodAsync(dataDto, userId);
+
+ // 4. Map and return
+ return MapToServiceDTO(result);
+}
+```
+
+### DataService Method Template
+```csharp
+public async Task MethodAsync(InputDataDTO dto, string userId)
+{
+ // 1. Create/modify entity
+ var entity = new Entity
+ {
+ Property = dto.Property,
+ CreatedBy = userId,
+ CreatedDate = DateTime.UtcNow,
+ ModifiedBy = userId,
+ ModifiedDate = DateTime.UtcNow
+ };
+
+ // 2. Database operation
+ _db.Entities.Add(entity);
+ await _db.SaveChangesAsync();
+
+ // 3. Map and return
+ return MapToDataDTO(entity);
+}
+```
+
+---
+
+## π Key Rules
+
+1. **Never skip layers** - Always go Controller β Service β DataService
+2. **No direct `_db` in Controllers** - Database access only in DataService
+3. **Validate in Service** - Use FluentValidation, not manual checks
+4. **Always check userId** - Return 401 if null
+5. **Catch ValidationException first** - Before generic Exception
+6. **Use interfaces everywhere** - For dependency injection
+7. **Set audit fields** - CreatedBy, CreatedDate, ModifiedBy, ModifiedDate
+8. **Return proper HTTP codes** - 200, 400, 401, 404, 500
+
+---
+
+## π Learn More
+
+- **Full Details**: See `BACKEND_ARCHITECTURE.md`
+- **Step-by-Step Guide**: See `QUICK_START_GUIDE.md`
+- **Working Examples**: Look at `ContactController`, `ContactService`, `ContactDataService`
diff --git a/Api.SeaHavenIndustries/Controllers/AccountController.cs b/Api.SeaHavenIndustries/Controllers/AccountController.cs
index 3e5c879..72b3a8c 100644
--- a/Api.SeaHavenIndustries/Controllers/AccountController.cs
+++ b/Api.SeaHavenIndustries/Controllers/AccountController.cs
@@ -1,9 +1,11 @@
ο»Ώusing Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
+using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SeaHaven.Services.Interfaces;
using SeaHaven.Services.DTOs;
+using System.Security.Claims;
[ApiController]
[Route("api/Account")]
@@ -33,7 +35,10 @@ public class AccountController : ControllerBase
Website = dto.Website
};
- var userId = User.Identity?.Name ?? "system";
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
await _accountService.CreateAccountAsync(createDto, userId);
return Ok(new DataResponse
@@ -42,13 +47,14 @@ public class AccountController : ControllerBase
Status = "200"
});
}
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
+ }
catch (Exception ex)
{
- return BadRequest(new Response
- {
- Status = "Error",
- Message = ex.Message
- });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
@@ -68,7 +74,10 @@ public class AccountController : ControllerBase
Website = dto.Website
};
- var userId = User.Identity?.Name ?? "system";
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
await _accountService.UpdateAccountAsync(dto.Id, updateDto, userId);
return Ok(new DataResponse
@@ -77,13 +86,18 @@ public class AccountController : ControllerBase
Status = "200"
});
}
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
+ }
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "Account not found" });
+ }
catch (Exception ex)
{
- return BadRequest(new Response
- {
- Status = "Error",
- Message = ex.Message
- });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
@@ -95,7 +109,10 @@ public class AccountController : ControllerBase
{
try
{
- var userId = User.Identity?.Name ?? "system";
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
await _accountService.DeleteAccountAsync(id, userId);
return Ok(new DataResponse
@@ -104,13 +121,13 @@ public class AccountController : ControllerBase
Status = "200"
});
}
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "Account not found" });
+ }
catch (Exception ex)
{
- return BadRequest(new Response
- {
- Status = "Error",
- Message = ex.Message
- });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
diff --git a/Api.SeaHavenIndustries/Controllers/AssetController.cs b/Api.SeaHavenIndustries/Controllers/AssetController.cs
index 88ccb5c..e671ad2 100644
--- a/Api.SeaHavenIndustries/Controllers/AssetController.cs
+++ b/Api.SeaHavenIndustries/Controllers/AssetController.cs
@@ -1,11 +1,13 @@
using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
+using FluentValidation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SeaHaven.Services.Interfaces;
using SeaHaven.Services.DTOs;
+using System.Security.Claims;
namespace Api.SeaHavenIndustries.Controllers
{
@@ -35,14 +37,22 @@ namespace Api.SeaHavenIndustries.Controllers
LocationId = asset.LocationId
};
- var userId = User.Identity?.Name ?? "system";
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
await _assetService.CreateAssetAsync(createDto, userId);
return Ok(new DataResponse { Message = "Asset Created Successfully", Status = "200" });
}
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
+ }
catch (Exception ex)
{
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
@@ -58,14 +68,26 @@ namespace Api.SeaHavenIndustries.Controllers
LocationId = asset.LocationId
};
- var userId = User.Identity?.Name ?? "system";
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
await _assetService.UpdateAssetAsync(asset.Id, updateDto, userId);
return Ok(new DataResponse { Message = "Asset Updated Successfully", Status = "200" });
}
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
+ }
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "Asset not found" });
+ }
catch (Exception ex)
{
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
@@ -75,13 +97,20 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
- var userId = User.Identity?.Name ?? "system";
- await _assetService.DeleteAssetAsync(id, userId);
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
+ await _assetService.DeleteAssetAsync(id, userId!);
return Ok(new DataResponse { Message = "Asset Deleted Successfully", Status = "200" });
}
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "Asset not found" });
+ }
catch (Exception ex)
{
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
diff --git a/Api.SeaHavenIndustries/Controllers/ContactController.cs b/Api.SeaHavenIndustries/Controllers/ContactController.cs
index d81153b..be53d37 100644
--- a/Api.SeaHavenIndustries/Controllers/ContactController.cs
+++ b/Api.SeaHavenIndustries/Controllers/ContactController.cs
@@ -1,5 +1,6 @@
ο»Ώusing Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
+using FluentValidation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
@@ -34,15 +35,22 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
- // β
Use DTO mapping method instead of manual property assignment
- var userId = _userManager.GetUserId(User) ?? "system";
+ var userId = _userManager.GetUserId(User);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
await _contactService.CreateContactAsync(contacts.ToServiceCreateDTO(), userId);
- return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
+ return Ok(new DataResponse { Message = "Contact created successfully", Status = "200" });
+ }
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
}
catch (Exception ex)
{
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
@@ -52,19 +60,26 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
- // β
Use DTO mapping method instead of manual property assignment
- var userId = _userManager.GetUserId(User) ?? "system";
+ var userId = _userManager.GetUserId(User);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
await _contactService.UpdateContactAsync(contacts.Id, contacts.ToServiceUpdateDTO(), userId);
- return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
+ return Ok(new DataResponse { Message = "Contact updated successfully", Status = "200" });
+ }
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
}
catch (KeyNotFoundException)
{
- return BadRequest(new Response { Status = "Error", Message = "Contact not found" });
+ return NotFound(new Response { Status = "Error", Message = "Contact not found" });
}
catch (Exception ex)
{
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
@@ -74,30 +89,28 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
- var userId = _userManager.GetUserId(User) ?? "system";
+ var userId = _userManager.GetUserId(User);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
- // β
Call Service layer (not DataService directly)
await _contactService.DeleteContactAsync(id, userId);
- return Ok(new DataResponse { Message = "Removed Successfully", Status = "200" });
+ return Ok(new DataResponse { Message = "Contact deleted successfully", Status = "200" });
}
catch (KeyNotFoundException)
{
- return BadRequest(new Response { Status = "Error", Message = "ID not matched!" });
+ return NotFound(new Response { Status = "Error", Message = "Contact not found" });
}
catch (Exception ex)
{
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
[HttpGet]
[Route("GetContactList")]
public async Task GetContactList(string? search ="", int page = 1, int pageSize = 10)
{
- // β
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);
@@ -116,7 +129,6 @@ namespace Api.SeaHavenIndustries.Controllers
[Route("GetContacts")]
public async Task GetContacts()
{
- // β
Call Service layer
var data = await _contactService.GetAllContactsAsync();
return Ok(data);
}
@@ -124,24 +136,15 @@ namespace Api.SeaHavenIndustries.Controllers
[Route("GetLocations")]
public async Task GetLocations()
{
- // β
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()
{
- // β TEMPORARY: Needs ICategoryService
return StatusCode(501, new Response { Status = "Error", Message = "Not Implemented - Needs CategoryService" });
}
@@ -149,11 +152,8 @@ namespace Api.SeaHavenIndustries.Controllers
[Route("GetUsers")]
public async Task GetUsers()
{
- // β 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")]
@@ -161,7 +161,6 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
- // β
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" });
@@ -178,7 +177,6 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
- // β
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" });
@@ -199,7 +197,6 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
- // β
Use Service layer
var userId = _userManager.GetUserId(User) ?? "system";
await _locationService.DeleteLocationAsync(Id, userId);
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
@@ -218,10 +215,7 @@ namespace Api.SeaHavenIndustries.Controllers
[Route("GetAddressbook")]
public async Task GetAddressbook(string? search = "", int page = 1, int pageSize = 10)
{
- // β
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);
diff --git a/Api.SeaHavenIndustries/Controllers/EmployeeController.cs b/Api.SeaHavenIndustries/Controllers/EmployeeController.cs
index 64e0bb8..2fa92e1 100644
--- a/Api.SeaHavenIndustries/Controllers/EmployeeController.cs
+++ b/Api.SeaHavenIndustries/Controllers/EmployeeController.cs
@@ -1,9 +1,11 @@
ο»Ώusing Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
+using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SeaHaven.Services.Interfaces;
using SeaHaven.Services.DTOs;
+using System.Security.Claims;
[ApiController]
[Route("api/Employee")]
@@ -18,9 +20,10 @@ public class EmployeeController : Controller
}
// -----------------------------------
- // POST: api/Employee/AddEmployees
+ // POST: api/Employee/AddEmployees or /Create
// -----------------------------------
[HttpPost("AddEmployees")]
+ [HttpPost("Create")]
public async Task AddEmployees(AddEmployee_DTO dto)
{
try
@@ -35,21 +38,30 @@ public class EmployeeController : Controller
Phone = dto.Phone
};
- var userId = User.Identity?.Name ?? "system";
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
await _employeeService.CreateEmployeeAsync(createDto, userId);
return Ok(new { Message = "Employee added successfully" });
}
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
+ }
catch (Exception ex)
{
- return BadRequest(new { Message = ex.Message });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
// -----------------------------------
- // POST: api/Employee/EditEmployees
+ // POST: api/Employee/EditEmployees or /Update
// -----------------------------------
[HttpPost("EditEmployees")]
+ [HttpPost("Update")]
public async Task EditEmployees(EditEmployee_DTO dto)
{
try
@@ -61,14 +73,26 @@ public class EmployeeController : Controller
JobTitleId = dto.JobTitleId
};
- var userId = User.Identity?.Name ?? "system";
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
await _employeeService.UpdateEmployeeAsync(dto.Id, updateDto, userId);
return Ok(new { Message = "Employee updated successfully" });
}
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
+ }
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "Employee not found" });
+ }
catch (Exception ex)
{
- return BadRequest(new { Message = ex.Message });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
@@ -106,14 +130,50 @@ public class EmployeeController : Controller
return Ok(viewModel);
}
// -----------------------------------
- // GET: api/Employee/DeleteEmployees?id=5
+ // GET: api/Employee/GetById?id=5
+ // -----------------------------------
+ [HttpGet("GetById")]
+ public async Task GetById(int id)
+ {
+ try
+ {
+ var employee = await _employeeService.GetEmployeeByIdAsync(id);
+
+ return Ok(new
+ {
+ id = employee.Id,
+ firstName = employee.FirstName,
+ lastName = employee.LastName,
+ jobTitle = employee.JobTitleName ?? "",
+ email = employee.Email ?? "",
+ phone = employee.Phone ?? "",
+ department = employee.DepartmentName ?? "",
+ status = "Active"
+ });
+ }
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "Employee not found" });
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
+ }
+ }
+
+ // -----------------------------------
+ // GET: api/Employee/DeleteEmployees or /Delete?id=5
// -----------------------------------
[HttpGet("DeleteEmployees")]
+ [HttpPost("Delete")]
public async Task DeleteEmployees(int id)
{
try
{
- var userId = User.Identity?.Name ?? "system";
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
await _employeeService.DeleteEmployeeAsync(id, userId);
return Ok(new DataResponse
@@ -130,14 +190,31 @@ public class EmployeeController : Controller
Message = ex.Message
});
}
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "Employee not found" });
+ }
catch (Exception ex)
{
- return BadRequest(new Response
- {
- Status = "Error",
- Message = ex.Message
- });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
+ // -----------------------------------
+ // GET: api/Employee/Dropdown
+ // -----------------------------------
+ [HttpGet("Dropdown")]
+ public async Task GetDropdown()
+ {
+ var pagedResult = await _employeeService.GetEmployeesPagedAsync(1, 1000, "");
+
+ var data = pagedResult.Items.Select(e => new
+ {
+ id = e.Id,
+ name = $"{e.FirstName} {e.LastName}".Trim()
+ });
+
+ return Ok(data);
+ }
+
}
diff --git a/Api.SeaHavenIndustries/Controllers/FollowUpController.cs b/Api.SeaHavenIndustries/Controllers/FollowUpController.cs
index de65e3e..02bdf0f 100644
--- a/Api.SeaHavenIndustries/Controllers/FollowUpController.cs
+++ b/Api.SeaHavenIndustries/Controllers/FollowUpController.cs
@@ -1,6 +1,7 @@
using System.Security.Claims;
using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
+using FluentValidation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
@@ -157,44 +158,75 @@ public class FollowUpController : ControllerBase
[HttpPost("Create")]
public async Task Create(FollowUp_DTO dto)
{
- var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
-
- var model = new FollowUps
+ try
{
- Reason = dto.Reason,
- ScheduleDate = dto.ScheduleDate,
- ScheduleStartTime = dto.ScheduleStartTime,
- EmployeeId = dto.EmployeeId,
- WorkOrderId = dto.WorkOrderId,
- AccountId = dto.AccountId,
- LocationId = dto.LocationId,
- Status = "Open",
- CreatedDate = DateTime.UtcNow,
- createdby = userId
- };
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
- await _followUpDataService.AddAsync(model);
+ var model = new FollowUps
+ {
+ Reason = dto.Reason,
+ ScheduleDate = dto.ScheduleDate,
+ ScheduleStartTime = dto.ScheduleStartTime,
+ EmployeeId = dto.EmployeeId,
+ WorkOrderId = dto.WorkOrderId,
+ AccountId = dto.AccountId,
+ LocationId = dto.LocationId,
+ Status = "Open",
+ CreatedDate = DateTime.UtcNow,
+ createdby = userId
+ };
- return Ok(new DataResponse { Status = "200", Message = "Follow Up created", Data = model.Id });
+ await _followUpDataService.AddAsync(model);
+
+ return Ok(new DataResponse { Status = "200", Message = "Follow Up created", Data = model.Id });
+ }
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
+ }
}
[HttpPost("Update")]
public async Task Update(FollowUp_DTO dto)
{
- var model = await _followUpDataService.GetByIdAsync(dto.Id);
- if (model == null) return NotFound();
+ try
+ {
+ var model = await _followUpDataService.GetByIdAsync(dto.Id);
+ if (model == null)
+ return NotFound(new Response { Status = "Error", Message = "Follow Up not found" });
- model.Reason = dto.Reason;
- model.ScheduleDate = dto.ScheduleDate;
- model.ScheduleStartTime = dto.ScheduleStartTime;
- model.EmployeeId = dto.EmployeeId;
- model.WorkOrderId = dto.WorkOrderId;
- model.AccountId = dto.AccountId;
- model.LocationId = dto.LocationId;
- model.LastModificationTime = DateTime.UtcNow;
+ model.Reason = dto.Reason;
+ model.ScheduleDate = dto.ScheduleDate;
+ model.ScheduleStartTime = dto.ScheduleStartTime;
+ model.EmployeeId = dto.EmployeeId;
+ model.WorkOrderId = dto.WorkOrderId;
+ model.AccountId = dto.AccountId;
+ model.LocationId = dto.LocationId;
+ model.LastModificationTime = DateTime.UtcNow;
- await _followUpDataService.UpdateAsync(model);
- return Ok(new DataResponse { Status = "200", Message = "Follow Up updated" });
+ await _followUpDataService.UpdateAsync(model);
+ return Ok(new DataResponse { Status = "200", Message = "Follow Up updated" });
+ }
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
+ }
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "Follow Up not found" });
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
+ }
}
[HttpPost("Complete")]
@@ -226,11 +258,23 @@ public class FollowUpController : ControllerBase
[HttpPost("Delete")]
public async Task Delete(int id)
{
- var model = await _followUpDataService.GetByIdAsync(id);
- if (model == null) return NotFound();
+ try
+ {
+ var model = await _followUpDataService.GetByIdAsync(id);
+ if (model == null)
+ return NotFound(new Response { Status = "Error", Message = "Follow Up not found" });
- await _followUpDataService.DeleteAsync(id);
+ await _followUpDataService.DeleteAsync(id);
- return Ok(new DataResponse { Status = "200", Message = "Follow Up deleted" });
+ return Ok(new DataResponse { Status = "200", Message = "Follow Up deleted" });
+ }
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "Follow Up not found" });
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
+ }
}
}
diff --git a/Api.SeaHavenIndustries/Controllers/LocationController.cs b/Api.SeaHavenIndustries/Controllers/LocationController.cs
index 102d206..4bfba79 100644
--- a/Api.SeaHavenIndustries/Controllers/LocationController.cs
+++ b/Api.SeaHavenIndustries/Controllers/LocationController.cs
@@ -1,11 +1,13 @@
using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
+using FluentValidation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SeaHaven.Services.Interfaces;
using SeaHaven.Services.DTOs;
using SeaHaven.DataServices.Interfaces;
+using System.Security.Claims;
namespace Api.SeaHavenIndustries.Controllers
{
@@ -108,9 +110,14 @@ namespace Api.SeaHavenIndustries.Controllers
await _locationDataService.AddAsync(location);
return Ok(new DataResponse { Message = "Location Created Successfully", Status = "200" });
}
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
+ }
catch (Exception ex)
{
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
@@ -121,14 +128,14 @@ namespace Api.SeaHavenIndustries.Controllers
{
var exist = await _locationDataService.GetByIdAsync(id);
if (exist == null)
- return BadRequest(new Response { Status = "Error", Message = "Location not found" });
+ return NotFound(new Response { Status = "Error", Message = "Location not found" });
exist.Name = model.Name;
exist.Title = model.Title;
exist.Address1 = model.Address;
exist.City = model.City;
exist.State = model.State;
- exist.Zip = model.ZipCode; // This is a property, not a method
+ exist.Zip = model.ZipCode;
exist.PhoneNumber = model.Phone;
exist.Email = model.ContactEmail;
exist.Status = model.Status;
@@ -137,9 +144,18 @@ namespace Api.SeaHavenIndustries.Controllers
await _locationDataService.UpdateAsync(exist);
return Ok(new DataResponse { Message = "Location Updated Successfully", Status = "200" });
}
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
+ }
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "Location not found" });
+ }
catch (Exception ex)
{
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
@@ -150,14 +166,18 @@ namespace Api.SeaHavenIndustries.Controllers
{
var exist = await _locationDataService.GetByIdAsync(id);
if (exist == null)
- return BadRequest(new Response { Status = "Error", Message = "Location not found" });
+ return NotFound(new Response { Status = "Error", Message = "Location not found" });
await _locationDataService.DeleteAsync(id);
return Ok(new DataResponse { Message = "Location Deleted Successfully", Status = "200" });
}
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "Location not found" });
+ }
catch (Exception ex)
{
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
diff --git a/Api.SeaHavenIndustries/Controllers/PMScheduleController.cs b/Api.SeaHavenIndustries/Controllers/PMScheduleController.cs
index 900f202..543e687 100644
--- a/Api.SeaHavenIndustries/Controllers/PMScheduleController.cs
+++ b/Api.SeaHavenIndustries/Controllers/PMScheduleController.cs
@@ -1,9 +1,11 @@
ο»Ώusing Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
+using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SeaHaven.Services.Interfaces;
using SeaHaven.Services.DTOs;
+using System.Security.Claims;
[ApiController]
[Route("api/PMSchedule")]
@@ -36,12 +38,46 @@ public class PMScheduleController : ControllerBase
return Ok(data);
}
+ [HttpGet("GetById")]
+ public async Task GetById([FromQuery] int id)
+ {
+ try
+ {
+ var pmSchedule = await _pmScheduleService.GetPMScheduleByIdAsync(id);
+ if (pmSchedule == null)
+ return NotFound(new Response { Status = "Error", Message = "PM Schedule not found" });
+
+ return Ok(new
+ {
+ Id = pmSchedule.Id,
+ Name = pmSchedule.Name,
+ Frequency = pmSchedule.Frequency,
+ StartDate = pmSchedule.StartDate,
+ IsActive = pmSchedule.IsActive,
+ AssetId = pmSchedule.AssetId,
+ LocationId = pmSchedule.LocationId,
+ Asset = pmSchedule.AssetName,
+ Location = pmSchedule.LocationName
+ });
+ }
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "PM Schedule not found" });
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
+ }
+ }
+
[HttpPost("Save")]
public async Task Save(PMSchedule_DTO dto)
{
try
{
- var userId = User.Identity?.Name ?? "system";
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
if (dto.Id == 0)
{
@@ -72,9 +108,40 @@ public class PMScheduleController : ControllerBase
return Ok();
}
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
+ }
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "PM Schedule not found" });
+ }
catch (Exception ex)
{
- return BadRequest(new { Message = ex.Message });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
+ }
+ }
+
+ [HttpPost("Delete")]
+ public async Task Delete([FromQuery] int id)
+ {
+ try
+ {
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
+ await _pmScheduleService.DeletePMScheduleAsync(id, userId);
+ return Ok(new DataResponse { Message = "PM Schedule deleted successfully", Status = "200" });
+ }
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "PM Schedule not found" });
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
}
diff --git a/Api.SeaHavenIndustries/Controllers/VendorController.cs b/Api.SeaHavenIndustries/Controllers/VendorController.cs
index 0b26f3e..9f6afc7 100644
--- a/Api.SeaHavenIndustries/Controllers/VendorController.cs
+++ b/Api.SeaHavenIndustries/Controllers/VendorController.cs
@@ -1,10 +1,12 @@
using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
+using FluentValidation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SeaHaven.Services.Interfaces;
using SeaHaven.Services.DTOs;
+using System.Security.Claims;
namespace Api.SeaHavenIndustries.Controllers
{
@@ -53,9 +55,13 @@ namespace Api.SeaHavenIndustries.Controllers
}
[HttpGet("{id}")]
- public async Task GetVendorById(int id)
+ [HttpGet("GetById")]
+ public async Task GetVendorById([FromQuery] int? id)
{
- var vendor = await _vendorService.GetVendorByIdAsync(id);
+ if (id == null)
+ return BadRequest(new Response { Status = "Error", Message = "Id is required" });
+
+ var vendor = await _vendorService.GetVendorByIdAsync(id.Value);
if (vendor == null)
return NotFound(new Response { Status = "Error", Message = "Vendor not found" });
@@ -64,6 +70,7 @@ namespace Api.SeaHavenIndustries.Controllers
}
[HttpPost]
+ [HttpPost("Create")]
public async Task Create([FromBody] Vendor_DTO model)
{
try
@@ -73,42 +80,88 @@ namespace Api.SeaHavenIndustries.Controllers
Name = model.CompanyName ?? throw new ArgumentException("CompanyName is required")
};
- var userId = User.Identity?.Name ?? "system";
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
await _vendorService.CreateVendorAsync(createDto, userId);
return Ok(new DataResponse { Message = "Vendor Created", Status = "200" });
}
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
+ }
catch (Exception ex)
{
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
[HttpPut("{id}")]
- public async Task Update(int id, [FromBody] EditVendor_DTO model)
+ [HttpPost("Update")]
+ public async Task Update([FromRoute] int? id, [FromBody] EditVendor_DTO model)
{
try
{
+ // For named route, id comes from model
+ int vendorId = id ?? model.Id;
+ if (vendorId == 0)
+ return BadRequest(new Response { Status = "Error", Message = "Vendor Id is required" });
+
var updateDto = new UpdateVendorDTO
{
Name = model.CompanyName
};
- var userId = User.Identity?.Name ?? "system";
- await _vendorService.UpdateVendorAsync(id, updateDto, userId);
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
+ await _vendorService.UpdateVendorAsync(vendorId, updateDto, userId);
return Ok(new DataResponse { Message = "Vendor Updated", Status = "200" });
}
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
+ }
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "Vendor not found" });
+ }
catch (Exception ex)
{
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
}
}
[HttpDelete("{id}")]
- public async Task Delete(int id)
+ [HttpPost("Delete")]
+ public async Task Delete([FromRoute] int? id, [FromQuery] int? queryId = null)
{
- var userId = User.Identity?.Name ?? "system";
- await _vendorService.DeleteVendorAsync(id, userId);
- return Ok(new DataResponse { Message = "Vendor Deleted", Status = "200" });
+ try
+ {
+ // Support both route parameter and query string
+ int vendorId = id ?? queryId ?? 0;
+ if (vendorId == 0)
+ return BadRequest(new Response { Status = "Error", Message = "Vendor Id is required" });
+
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ if (userId == null)
+ return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
+
+ await _vendorService.DeleteVendorAsync(vendorId, userId);
+ return Ok(new DataResponse { Message = "Vendor Deleted", Status = "200" });
+ }
+ catch (KeyNotFoundException)
+ {
+ return NotFound(new Response { Status = "Error", Message = "Vendor not found" });
+ }
+ catch (Exception ex)
+ {
+ return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
+ }
}
[HttpGet("Dropdown")]
diff --git a/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs b/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs
index 946d674..57eb4b8 100644
--- a/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs
+++ b/Api.SeaHavenIndustries/Controllers/WorkOrderController.cs
@@ -1,5 +1,7 @@
ο»Ώusing Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
+using Data.SeaHavenIndustries.Enums;
+using FluentValidation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Hosting;
@@ -13,6 +15,8 @@ using System.IO;
using System.Net.Http.Headers;
using System.Security.Claims;
using static System.Runtime.InteropServices.JavaScript.JSType;
+using SeaHaven.Services.Interfaces;
+using SeaHaven.Services.DTOs;
using SeaHaven.DataServices.Interfaces;
namespace Api.SeaHavenIndustries.Controllers
@@ -24,8 +28,15 @@ namespace Api.SeaHavenIndustries.Controllers
public class WorkOrderController : Controller
{
private readonly UserManager _userManager;
+ private readonly IWorkOrderService _workOrderService;
private readonly IWorkOrderDataService _workOrderDataService;
- private readonly ApplicationDbContext _db; // Keep for complex operations, file handling, dispatch, comments
+ private readonly ICommentDataService _commentDataService;
+ private readonly IQuotesDataService _quotesDataService;
+ private readonly IUserDataService _userDataService;
+ private readonly IDispatchDataService _dispatchDataService;
+ private readonly IVendorDataService _vendorDataService;
+ private readonly ITaskListTemplateDataService _taskListTemplateDataService;
+ private readonly ApplicationDbContext _db;
private readonly IWebHostEnvironment _webHostEnvironment;
IHttpContextAccessor _httpContext;
private readonly Helper.SendMessage _sendMessage;
@@ -34,7 +45,14 @@ namespace Api.SeaHavenIndustries.Controllers
public WorkOrderController(
UserManager userManager,
+ IWorkOrderService workOrderService,
IWorkOrderDataService workOrderDataService,
+ ICommentDataService commentDataService,
+ IQuotesDataService quotesDataService,
+ IUserDataService userDataService,
+ IDispatchDataService dispatchDataService,
+ IVendorDataService vendorDataService,
+ ITaskListTemplateDataService taskListTemplateDataService,
ApplicationDbContext db,
IWebHostEnvironment webHostEnvironment,
IHttpContextAccessor httpContext,
@@ -43,7 +61,14 @@ namespace Api.SeaHavenIndustries.Controllers
IConfiguration config)
{
_userManager = userManager;
+ _workOrderService = workOrderService;
_workOrderDataService = workOrderDataService;
+ _commentDataService = commentDataService;
+ _quotesDataService = quotesDataService;
+ _userDataService = userDataService;
+ _dispatchDataService = dispatchDataService;
+ _vendorDataService = vendorDataService;
+ _taskListTemplateDataService = taskListTemplateDataService;
_db = db;
_webHostEnvironment = webHostEnvironment;
_httpContext = httpContext;
@@ -53,138 +78,87 @@ namespace Api.SeaHavenIndustries.Controllers
}
[HttpPost]
[Route("AddWorkorder")]
- public async Task Addworkorder([FromBody] Workorder_DTO model)
+ public async Task Addworkorder([FromForm] Workorder_DTO model)
{
try
{
- string userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
- WorkOrder workOrder = new WorkOrder();
- workOrder.InternalWONumber = await GenerateInternalWONumber();
- workOrder.WorkerOrderNumber = model.WorkerOrderNumber;
- workOrder.WorkerOrderTitle = model.WorkerOrderTitle;
- workOrder.Description = model.Description;
- workOrder.TT = model.TT;
- workOrder.PO = model.PO;
- workOrder.AssignTo = model.AssignTo;
- workOrder.DueDate = model.DueDate;
- workOrder.Priority = model.Priority;
- workOrder.istemplate = model.istemplate;
- //workOrder.CategoryId = model.CategoryId;
- workOrder.LocationId = model.LocationId;
- workOrder.Status = "Open";
- workOrder.istemplate = model.istemplate;
- workOrder.BeforPhotoIssue = model.BeforPhotoIssue;
- workOrder.AfterPhotoIssue = model.AfterPhotoIssue;
- workOrder.SignOffName = model.SignOffName;
- workOrder.SignOffSignature = model.SignOffSignature;
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
- workOrder.WorkOrderContacts = workOrder.WorkOrderContacts == null ? new List() : workOrder.WorkOrderContacts;
- List contactIds = model.GetContactIds();
- workOrder.WorkOrderCategories = workOrder.WorkOrderCategories == null ? new List() : workOrder.WorkOrderCategories;
- List categoryIds = model.GetCategoryIds();
-
- // Create new records for the associations (pseudo-code)
- foreach (int contactId in contactIds)
+ var createDto = new CreateWorkOrderDTO
{
- // Create association with contact
- workOrder.WorkOrderContacts.Add(new WorkOrderContacts { ContactId = contactId });
+ WorkerOrderTitle = model.WorkerOrderTitle ?? "",
+ Description = model.Description,
+ Status = WorkOrderStatus.Open,
+ Priority = model.Priority ?? WorkOrderPriority.Medium,
+ LocationId = model.LocationId,
+ AssignTo = model.AssignTo,
+ DueDate = model.DueDate
+ };
+
+ var workOrderId = await _workOrderService.CreateWorkOrderAsync(createDto, userId!);
+
+ if (model.ContactIds != null && model.ContactIds.Any())
+ {
+ await _workOrderDataService.AddWorkOrderContactsAsync(workOrderId, model.ContactIds);
}
- foreach (int categoryId in categoryIds)
+ if (model.CategoryIds != null && model.CategoryIds.Any())
{
- // Create association with category
- workOrder.WorkOrderCategories.Add(new WorkOrderCategories { CategoryId = categoryId });
+ await _workOrderDataService.AddWorkOrderCategoriesAsync(workOrderId, model.CategoryIds);
}
- #region Images of workorder
- workOrder.workOrderAttachments = workOrder.workOrderAttachments == null ? new List() : workOrder.workOrderAttachments;
if (model.Attachments != null)
{
foreach (var attachment in model.Attachments)
{
- string fileName = ContentDispositionHeaderValue.Parse(attachment.ContentDisposition).FileName.Trim('"');
- string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
-
- string uploadProfilePath = Path.Combine("Assets", "Documents");
- string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
-
- Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
-
- // Copy the file to the server
- using (var ms = System.IO.File.Create(fullPath))
+ var fileUrl = await SaveFileAsync(attachment);
+ await _workOrderDataService.AddWorkOrderAttachmentAsync(new WorkOrderAttachments
{
- await attachment.CopyToAsync(ms);
- }
-
- // Update the user's image URL
- var request = _httpContext.HttpContext.Request;
- var domain = $"{request.Scheme}://{request.Host}";
- var imagesPath = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
- workOrder.workOrderAttachments.Add(new WorkOrderAttachments { Attachments = imagesPath });
+ WorkorderId = workOrderId,
+ Attachments = fileUrl
+ });
}
}
if (model.BeforPhotoAttachment != null)
{
- string fileName = ContentDispositionHeaderValue.Parse(model.BeforPhotoAttachment.ContentDisposition).FileName.Trim('"');
- string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
-
- string uploadProfilePath = Path.Combine("Assets", "Documents");
- string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
-
- Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
- // Copy the file to the server
- using (var ms = System.IO.File.Create(fullPath))
+ var fileUrl = await SaveFileAsync(model.BeforPhotoAttachment);
+ var workOrder = await _workOrderDataService.GetByIdAsync(workOrderId);
+ if (workOrder != null)
{
- await model.BeforPhotoAttachment.CopyToAsync(ms);
+ workOrder.BeforPhotoAttachment = fileUrl;
+ await _workOrderDataService.UpdateAsync(workOrder);
}
- // Update the user's image URL
- var request = _httpContext.HttpContext.Request;
- var domain = $"{request.Scheme}://{request.Host}";
- workOrder.BeforPhotoAttachment = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
}
+
if (model.AfterPhotoAttachment != null)
{
- string fileName = ContentDispositionHeaderValue.Parse(model.AfterPhotoAttachment.ContentDisposition).FileName.Trim('"');
- string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
-
- string uploadProfilePath = Path.Combine("Assets", "Documents");
- string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
-
- Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
- // Copy the file to the server
- using (var ms = System.IO.File.Create(fullPath))
+ var fileUrl = await SaveFileAsync(model.AfterPhotoAttachment);
+ var workOrder = await _workOrderDataService.GetByIdAsync(workOrderId);
+ if (workOrder != null)
{
- await model.AfterPhotoAttachment.CopyToAsync(ms);
+ workOrder.AfterPhotoAttachment = fileUrl;
+ await _workOrderDataService.UpdateAsync(workOrder);
}
- // Update the user's image URL
- var request = _httpContext.HttpContext.Request;
- var domain = $"{request.Scheme}://{request.Host}";
- workOrder.AfterPhotoAttachment = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
}
+
if (model.SignOffAttachment != null)
{
- string fileName = ContentDispositionHeaderValue.Parse(model.SignOffAttachment.ContentDisposition).FileName.Trim('"');
- string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
-
- string uploadProfilePath = Path.Combine("Assets", "Documents");
- string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
-
- Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
- // Copy the file to the server
- using (var ms = System.IO.File.Create(fullPath))
+ var fileUrl = await SaveFileAsync(model.SignOffAttachment);
+ var workOrder = await _workOrderDataService.GetByIdAsync(workOrderId);
+ if (workOrder != null)
{
- await model.SignOffAttachment.CopyToAsync(ms);
+ workOrder.SignOffAttachment = fileUrl;
+ await _workOrderDataService.UpdateAsync(workOrder);
}
- // Update the user's image URL
- var request = _httpContext.HttpContext.Request;
- var domain = $"{request.Scheme}://{request.Host}";
- workOrder.SignOffAttachment = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
}
- #endregion
- _db.Add(workOrder);
- _db.SaveChanges();
- return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
+
+ return Ok(new DataResponse { Message = "Work order created successfully", Status = "200", Data = workOrderId });
+ }
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
}
catch (Exception ex)
{
@@ -196,186 +170,97 @@ namespace Api.SeaHavenIndustries.Controllers
[HttpPost]
[HttpPut("{id:int}")]
[Route("EditWorkorder")]
- public async Task Editworkorder([FromBody] EditWorkorder_DTO model)
+ public async Task Editworkorder([FromForm] EditWorkorder_DTO model)
{
try
{
- var workOrder = _db.workOrders.Where(w => w.Id == model.Id).FirstOrDefault();
- if (workOrder == null)
- return BadRequest(new Response { Status = "Error", Message = "Work order not found" });
+ if (!model.Id.HasValue)
+ return BadRequest(new Response { Status = "Error", Message = "Work order ID is required" });
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
- var now = DateTime.UtcNow;
- void LogChange(string field, string? oldVal, string? newVal)
+
+ var updateDto = new UpdateWorkOrderDTO
{
- if (oldVal == newVal) return;
- _db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
- {
- WorkOrderId = workOrder.Id,
- UserId = userId,
- FieldName = field,
- OldValue = oldVal ?? "",
- NewValue = newVal ?? "",
- Action = "edit",
- CreatedAt = now
- });
+ WorkerOrderTitle = model.WorkerOrderTitle,
+ Description = model.Description,
+ Status = model.Status,
+ Priority = model.Priority,
+ LocationId = model.LocationId,
+ AssignTo = model.AssignTo,
+ DueDate = model.DueDate,
+ CompletedDate = model.CompletedDate
+ };
+
+ var success = await _workOrderService.UpdateWorkOrderAsync(model.Id.Value, updateDto, userId!);
+ if (!success)
+ return BadRequest(new Response { Status = "Error", Message = "Work order not found" });
+
+ await _workOrderDataService.DeleteWorkOrderContactsAsync(model.Id.Value);
+ await _workOrderDataService.DeleteWorkOrderCategoriesAsync(model.Id.Value);
+
+ if (model.ContactIds != null && model.ContactIds.Any())
+ {
+ await _workOrderDataService.AddWorkOrderContactsAsync(model.Id.Value, model.ContactIds);
}
- LogChange("WorkOrderNumber", workOrder.WorkerOrderNumber, model.WorkerOrderNumber);
- LogChange("Title", workOrder.WorkerOrderTitle, model.WorkerOrderTitle);
- LogChange("Description", workOrder.Description, model.Description);
- LogChange("TT", workOrder.TT, model.TT);
- LogChange("PO", workOrder.PO, model.PO);
- LogChange("AssignTo", workOrder.AssignTo, model.AssignTo);
- LogChange("DueDate", workOrder.DueDate?.ToString("yyyy-MM-dd"), model.DueDate?.ToString("yyyy-MM-dd"));
- LogChange("Priority", workOrder.Priority, model.Priority);
- LogChange("LocationId", workOrder.LocationId?.ToString(), model.LocationId?.ToString());
- LogChange("Status", workOrder.Status, model.Status);
- LogChange("Problem", workOrder.Problem, model.Problem);
- LogChange("Trade", workOrder.Trade, model.Trade);
- LogChange("SubTrade", workOrder.SubTrade, model.SubTrade);
- LogChange("VendorNTE", workOrder.VendorNTE?.ToString("F2"), model.VendorNTE?.ToString("F2"));
- LogChange("ScheduledDate", workOrder.ScheduledDate?.ToString("yyyy-MM-dd"), model.ScheduledDate?.ToString("yyyy-MM-dd"));
- LogChange("CompletedDate", workOrder.CompletedDate?.ToString("yyyy-MM-dd"), model.CompletedDate?.ToString("yyyy-MM-dd"));
- LogChange("Source", workOrder.Source, model.Source);
-
- workOrder.WorkerOrderNumber = model.WorkerOrderNumber;
- workOrder.WorkerOrderTitle = model.WorkerOrderTitle;
- workOrder.Description = model.Description;
- workOrder.TT = model.TT;
- workOrder.PO = model.PO;
- workOrder.AssignTo = model.AssignTo;
- workOrder.DueDate = model.DueDate;
- workOrder.Priority = model.Priority;
- workOrder.LocationId = model.LocationId;
- workOrder.Status = model.Status;
- workOrder.Problem = model.Problem;
- workOrder.Trade = model.Trade;
- workOrder.SubTrade = model.SubTrade;
- workOrder.VendorNTE = model.VendorNTE;
- workOrder.ScheduledDate = model.ScheduledDate;
- workOrder.CompletedDate = model.CompletedDate;
- workOrder.Source = model.Source;
- workOrder.BeforPhotoIssue = model.BeforPhotoIssue;
- workOrder.AfterPhotoIssue = model.AfterPhotoIssue;
- workOrder.SignOffName = model.SignOffName;
- workOrder.SignOffSignature = model.SignOffSignature;
- var existcontact = _db.WorkOrderContacts.Where(w => w.WorkorderId == model.Id).ToList();
- if (existcontact.Count() > 0)
+ if (model.CategoryIds != null && model.CategoryIds.Any())
{
- _db.WorkOrderContacts.RemoveRange(existcontact);
- }
- var existcategory = _db.workOrderCategories.Where(w => w.WorkorderId == model.Id).ToList();
- if (existcategory.Count() > 0)
- {
- _db.workOrderCategories.RemoveRange(existcategory);
- }
- workOrder.WorkOrderContacts = workOrder.WorkOrderContacts == null ? new List() : workOrder.WorkOrderContacts;
- List contactIds = model.GetContactIds();
-
- workOrder.WorkOrderCategories = workOrder.WorkOrderCategories == null ? new List() : workOrder.WorkOrderCategories;
- List categoryIds = model.GetCategoryIds();
-
- // Create new records for the associations (pseudo-code)
- foreach (int contactId in contactIds)
- {
- // Create association with contact
- workOrder.WorkOrderContacts.Add(new WorkOrderContacts { ContactId = contactId });
+ await _workOrderDataService.AddWorkOrderCategoriesAsync(model.Id.Value, model.CategoryIds);
}
- foreach (int categoryId in categoryIds)
- {
- // Create association with category
- workOrder.WorkOrderCategories.Add(new WorkOrderCategories { CategoryId = categoryId });
- }
-
- #region Images of workorder
- workOrder.workOrderAttachments = workOrder.workOrderAttachments == null ? new List() : workOrder.workOrderAttachments;
if (model.Attachments != null)
{
foreach (var attachment in model.Attachments)
{
- string fileName = ContentDispositionHeaderValue.Parse(attachment.ContentDisposition).FileName.Trim('"');
- string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
-
- string uploadProfilePath = Path.Combine("Assets", "Documents");
- string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
-
- Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
-
- using (var ms = System.IO.File.Create(fullPath))
+ var fileUrl = await SaveFileAsync(attachment);
+ await _workOrderDataService.AddWorkOrderAttachmentAsync(new WorkOrderAttachments
{
- await attachment.CopyToAsync(ms);
- }
-
- var request = _httpContext.HttpContext.Request;
- var domain = $"{request.Scheme}://{request.Host}";
- var imagesPath = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
- workOrder.workOrderAttachments.Add(new WorkOrderAttachments { Attachments = imagesPath });
+ WorkorderId = model.Id.Value,
+ Attachments = fileUrl
+ });
}
}
if (model.BeforPhotoAttachment != null)
{
- string fileName = ContentDispositionHeaderValue.Parse(model.BeforPhotoAttachment.ContentDisposition).FileName.Trim('"');
- string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
-
- string uploadProfilePath = Path.Combine("Assets", "Documents");
- string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
-
- Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
- // Copy the file to the server
- using (var ms = System.IO.File.Create(fullPath))
+ var fileUrl = await SaveFileAsync(model.BeforPhotoAttachment);
+ var workOrder = await _workOrderDataService.GetByIdAsync(model.Id.Value);
+ if (workOrder != null)
{
- await model.BeforPhotoAttachment.CopyToAsync(ms);
+ workOrder.BeforPhotoAttachment = fileUrl;
+ await _workOrderDataService.UpdateAsync(workOrder);
}
- // Update the user's image URL
- var request = _httpContext.HttpContext.Request;
- var domain = $"{request.Scheme}://{request.Host}";
- workOrder.BeforPhotoAttachment = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
}
+
if (model.AfterPhotoAttachment != null)
{
- string fileName = ContentDispositionHeaderValue.Parse(model.AfterPhotoAttachment.ContentDisposition).FileName.Trim('"');
- string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
-
- string uploadProfilePath = Path.Combine("Assets", "Documents");
- string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
-
- Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
- // Copy the file to the server
- using (var ms = System.IO.File.Create(fullPath))
+ var fileUrl = await SaveFileAsync(model.AfterPhotoAttachment);
+ var workOrder = await _workOrderDataService.GetByIdAsync(model.Id.Value);
+ if (workOrder != null)
{
- await model.AfterPhotoAttachment.CopyToAsync(ms);
+ workOrder.AfterPhotoAttachment = fileUrl;
+ await _workOrderDataService.UpdateAsync(workOrder);
}
- // Update the user's image URL
- var request = _httpContext.HttpContext.Request;
- var domain = $"{request.Scheme}://{request.Host}";
- workOrder.AfterPhotoAttachment = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
}
+
if (model.SignOffAttachment != null)
{
- string fileName = ContentDispositionHeaderValue.Parse(model.SignOffAttachment.ContentDisposition).FileName.Trim('"');
- string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
-
- string uploadProfilePath = Path.Combine("Assets", "Documents");
- string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
-
- Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
- // Copy the file to the server
- using (var ms = System.IO.File.Create(fullPath))
+ var fileUrl = await SaveFileAsync(model.SignOffAttachment);
+ var workOrder = await _workOrderDataService.GetByIdAsync(model.Id.Value);
+ if (workOrder != null)
{
- await model.SignOffAttachment.CopyToAsync(ms);
+ workOrder.SignOffAttachment = fileUrl;
+ await _workOrderDataService.UpdateAsync(workOrder);
}
- // Update the user's image URL
- var request = _httpContext.HttpContext.Request;
- var domain = $"{request.Scheme}://{request.Host}";
- workOrder.SignOffAttachment = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
}
- #endregion
- _db.SaveChanges();
+
return Ok(new DataResponse { Message = "Updated Successfully", Status = "200" });
}
+ catch (ValidationException vex)
+ {
+ var errors = string.Join(", ", vex.Errors.Select(e => e.ErrorMessage));
+ return BadRequest(new Response { Status = "Validation Error", Message = errors });
+ }
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
@@ -412,7 +297,7 @@ namespace Api.SeaHavenIndustries.Controllers
[HttpDelete]
[Route("DeleteWorkorder")]
- public IActionResult Deleteworkorder([FromBody] EditWorkorder_DTO model)
+ public async Task Deleteworkorder([FromBody] EditWorkorder_DTO model)
{
int id = model.Id.HasValue ? model.Id.Value : 0;
if (id == 0)
@@ -421,46 +306,20 @@ namespace Api.SeaHavenIndustries.Controllers
}
try
{
- var exist = _db.workOrders.Where(w => w.Id == id).FirstOrDefault();
- if (exist != null)
- {
- var quotes = _db.Quotes.Where(w => w.WorkorderId == id).ToList();
- if (quotes.Count() > 0)
- {
- foreach (var itm in quotes)
- {
- var existquotes = _db.Quotes.Where(w => w.Id == itm.Id).FirstOrDefault();
- existquotes.WorkorderId = null;
- }
- }
- var attachments = _db.workOrderAttachments.Where(w => w.WorkorderId == id).ToList();
- if (attachments.Count() > 0)
- {
- _db.RemoveRange(attachments);
- }
- var contacts = _db.WorkOrderContacts.Where(w => w.WorkorderId == id).ToList();
- if (contacts.Count() > 0)
- {
- _db.RemoveRange(contacts);
- }
- var category = _db.workOrderCategories.Where(w => w.WorkorderId == id).ToList();
- if (category.Count() > 0)
- {
- _db.RemoveRange(category);
- }
- var comments = _db.Comments.Where(w => w.WorkerOrderId == id).ToList();
- if (comments.Count() > 0)
- {
- _db.RemoveRange(comments);
- }
- _db.Remove(exist);
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+ var success = await _workOrderService.DeleteWorkOrderAsync(id, userId!);
- }
- else
+ if (!success)
+ return BadRequest("Work order not found!");
+
+ await _quotesDataService.UnlinkFromWorkOrderAsync(id);
+
+ var comments = await _commentDataService.GetByWorkOrderIdAsync(id);
+ foreach (var comment in comments)
{
- return BadRequest("ID not matched!");
+ await _commentDataService.DeleteAsync(comment.Id);
}
- _db.SaveChanges();
+
return Ok(new DataResponse { Message = "Removed Successfully", Status = "200" });
}
catch (Exception ex)
@@ -782,25 +641,30 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
- var data = await _db.workOrders.Where(w => w.Id == id).FirstOrDefaultAsync();
- if (data == null)
+ var workOrder = await _workOrderDataService.GetByIdAsync(id);
+ if (workOrder == null)
return BadRequest(new Response { Status = "Error", Message = "Work order not found" });
- if (data.Status != status)
+
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+
+ if (workOrder.Status != status)
{
- _db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
+ await _workOrderDataService.AddAuditLogAsync(new WorkOrderAuditLog
{
WorkOrderId = id,
- UserId = User.FindFirstValue(ClaimTypes.NameIdentifier),
+ UserId = userId,
FieldName = "Status",
- OldValue = data.Status ?? "",
+ OldValue = workOrder.Status ?? "",
NewValue = status,
- Action = "status_change",
+ Action = AuditAction.StatusChange.ToString(),
CreatedAt = DateTime.UtcNow
});
}
- data.Status = status;
- await _db.SaveChangesAsync();
- return Ok(new DataResponse { Message = "Updated Successfully", Status = "200", Data = data });
+
+ workOrder.Status = status;
+ await _workOrderDataService.UpdateAsync(workOrder);
+
+ return Ok(new DataResponse { Message = "Updated Successfully", Status = "200", Data = WorkOrderDTO.MapToModel(workOrder) });
}
catch (Exception ex)
{
@@ -814,28 +678,33 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
- var data = await _db.workOrders.Where(w => w.Id == id).FirstOrDefaultAsync();
- if (data == null)
+ var workOrder = await _workOrderDataService.GetByIdAsync(id);
+ if (workOrder == null)
return BadRequest(new Response { Status = "Error", Message = "Work order not found" });
- if (data.AssignTo != assignTo)
+
+ var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
+
+ if (workOrder.AssignTo != assignTo)
{
- var oldUser = data.AssignTo != null ? await _db.Users.FindAsync(data.AssignTo) : null;
- var newUser = assignTo != null ? await _db.Users.FindAsync(assignTo) : null;
- _db.WorkOrderAuditLogs.Add(new WorkOrderAuditLog
+ var oldUser = workOrder.AssignTo != null ? await _userDataService.GetByIdAsync(workOrder.AssignTo) : null;
+ var newUser = assignTo != null ? await _userDataService.GetByIdAsync(assignTo) : null;
+
+ await _workOrderDataService.AddAuditLogAsync(new WorkOrderAuditLog
{
WorkOrderId = id,
- UserId = User.FindFirstValue(ClaimTypes.NameIdentifier),
+ UserId = userId,
FieldName = "AssignedTo",
OldValue = oldUser != null ? (oldUser.FirstName + " " + oldUser.LastName).Trim() : "",
NewValue = newUser != null ? (newUser.FirstName + " " + newUser.LastName).Trim() : "Unassigned",
- Action = "assignment_change",
+ Action = AuditAction.AssignmentChange.ToString(),
CreatedAt = DateTime.UtcNow
});
}
- data.AssignTo = assignTo;
- await _db.SaveChangesAsync();
- var assignedUser = assignTo != null ? await _db.Users.FindAsync(assignTo) : null;
+ workOrder.AssignTo = assignTo;
+ await _workOrderDataService.UpdateAsync(workOrder);
+
+ var assignedUser = assignTo != null ? await _userDataService.GetByIdAsync(assignTo) : null;
var assignedName = assignedUser != null ? (assignedUser.FirstName + " " + assignedUser.LastName).Trim() : "";
return Ok(new { message = "Updated Successfully", assignedTo = assignedName });
}
@@ -852,34 +721,23 @@ namespace Api.SeaHavenIndustries.Controllers
try
{
var userId = _httpContext?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "";
- Comments comments = new Comments();
- comments.CreatedDate = DateTime.Now;
- comments.UserId = userId;
- comments.WorkerOrderId = model.WorkorderId;
- comments.Commenttext = model.Text;
- comments.CommentType = model.CommentType ?? "vendor";
+
+ var comment = new Comments
+ {
+ UserId = userId,
+ WorkerOrderId = model.WorkorderId,
+ Commenttext = model.Text,
+ CommentType = model.CommentType ?? "vendor"
+ };
+
if (model.Document != null)
{
- string fileName = ContentDispositionHeaderValue.Parse(model.Document.ContentDisposition).FileName.Trim('"');
- string uniqueFileName = $"{Guid.NewGuid()}_{fileName}";
-
- string uploadProfilePath = Path.Combine("Assets", "Documents");
- string fullPath = Path.Combine(_webHostEnvironment.WebRootPath, uploadProfilePath, uniqueFileName);
-
- Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
- // Copy the file to the server
- using (var ms = System.IO.File.Create(fullPath))
- {
- await model.Document.CopyToAsync(ms);
- }
- // Update the user's image URL
- var request = _httpContext.HttpContext.Request;
- var domain = $"{request.Scheme}://{request.Host}";
- comments.Documents = domain + "/" + Path.Combine(uploadProfilePath, uniqueFileName);
+ var fileUrl = await SaveFileAsync(model.Document);
+ comment.Documents = fileUrl;
}
- _db.Comments.Add(comments);
- await _db.SaveChangesAsync();
- return Ok(new DataResponse { Message = "Updated Successfully", Status = "200", Data = comments });
+
+ var saved = await _commentDataService.AddAsync(comment);
+ return Ok(new DataResponse { Message = "Updated Successfully", Status = "200", Data = saved });
}
catch (Exception ex)
{
@@ -896,22 +754,21 @@ namespace Api.SeaHavenIndustries.Controllers
var userId = _httpContext?.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "";
var comment = new Comments
{
- CreatedDate = DateTime.UtcNow,
UserId = userId,
WorkerOrderId = model.WorkorderId,
Commenttext = model.Text,
CommentType = model.CommentType ?? "internal",
};
- _db.Comments.Add(comment);
- await _db.SaveChangesAsync();
- var user = await _db.Users.FindAsync(userId);
+ var saved = await _commentDataService.AddAsync(comment);
+ var user = await _userDataService.GetByIdAsync(userId);
+
return Ok(new
{
- comment.Id,
- comment.CreatedDate,
- comment.Commenttext,
- comment.CommentType,
+ saved.Id,
+ saved.CreatedDate,
+ saved.Commenttext,
+ saved.CommentType,
userName = user != null ? (user.FirstName + " " + user.LastName).Trim() : ""
});
}
@@ -925,13 +782,14 @@ namespace Api.SeaHavenIndustries.Controllers
[Route("GetComments")]
public async Task GetComments()
{
- var data = await _db.Comments.Include(i => i.ApplicationUser).Select(s => new
+ var comments = await _commentDataService.GetAllAsync();
+ var data = comments.Select(s => new
{
s.Id,
s.Commenttext,
FirstName = s.ApplicationUser != null ? s.ApplicationUser.FirstName : "",
s.Documents
- }).ToListAsync();
+ }).ToList();
return Ok(data);
}
@@ -939,47 +797,42 @@ namespace Api.SeaHavenIndustries.Controllers
[Route("GetCommentsByWorkorderId")]
public async Task GetCommentsByWorkorderId(int woid)
{
- var data = await _db.Comments.Where(w => w.WorkerOrderId == woid).Include(i => i.ApplicationUser).Select(s => new
+ var comments = await _commentDataService.GetByWorkOrderIdAsync(woid);
+ var data = comments.Select(s => new
{
s.Id,
s.Commenttext,
FirstName = s.ApplicationUser != null ? s.ApplicationUser.FirstName : "",
s.Documents
- }).ToListAsync();
+ }).ToList();
return Ok(data);
}
[HttpGet]
[Route("GetworkordersDD")]
public async Task GetworkordersDD()
{
- _db.ChangeTracker.Clear();
-
- var data = await _db.workOrders.Where(w => w.istemplate != true).Include(w => w.Locations).Include(w => w.AssignToUser).ToListAsync();
- return Ok(data);
+ var data = await _workOrderDataService.GetAllAsync();
+ return Ok(data.Where(w => w.istemplate != true));
}
[HttpGet]
[Route("Getworkorders")]
public async Task Getworkorders()
{
- _db.ChangeTracker.Clear();
- var data = await _db.workOrders
- .Where(w => w.istemplate != true)
- .AsSplitQuery()
- .Include(w => w.Locations)
- .Include(w => w.AssignToUser).Select(s => new WorkorderFilterVM
- {
- Id = s.Id,
- Priority = s.Priority,
- Status = s.Status,
- WorkerOrderNumber = s.WorkerOrderNumber,
- WorkerOrderTitle = s.WorkerOrderTitle,
- DueDate = s.DueDate,
- AssignToUserName = s.AssignToUser != null ? s.AssignToUser.FirstName : "",
- LocationName = s.Locations != null ? s.Locations.Name : "",
- LocationId = s.LocationId,
- PO = s.PO,
- TT = s.TT,
- }).ToListAsync();
+ var workOrders = await _workOrderDataService.GetAllWithDetailsAsync();
+ var data = workOrders.Select(s => new WorkorderFilterVM
+ {
+ Id = s.Id,
+ Priority = s.Priority,
+ Status = s.Status,
+ WorkerOrderNumber = s.WorkerOrderNumber,
+ WorkerOrderTitle = s.WorkerOrderTitle,
+ DueDate = s.DueDate,
+ AssignToUserName = s.AssignToUser != null ? s.AssignToUser.FirstName : "",
+ LocationName = s.Locations != null ? s.Locations.Name : "",
+ LocationId = s.LocationId,
+ PO = s.PO,
+ TT = s.TT,
+ }).ToList();
return Ok(data);
}
[HttpPost]
@@ -988,40 +841,28 @@ namespace Api.SeaHavenIndustries.Controllers
{
try
{
- var workOrders = await _db.workOrders.Include(w => w.Locations)
- .Where(w => model.WorkOrderIds.Contains(w.Id))
- .ToListAsync();
+ var allWorkOrders = await _workOrderDataService.GetAllWithDetailsAsync();
+ var workOrders = allWorkOrders.Where(w => model.WorkOrderIds.Contains(w.Id)).ToList();
+
if (workOrders.Count == 0)
return BadRequest(new Response { Status = "Error", Message = "No work orders found" });
var primaryWO = workOrders.First();
- var lastPO = await _db.Dispatches
- .Where(d => d.PONumber != null)
- .OrderByDescending(d => d.PONumber)
- .Select(d => d.PONumber)
- .FirstOrDefaultAsync();
- int nextPO = 1;
- if (lastPO != null && lastPO.StartsWith("VPO-") && int.TryParse(lastPO.Substring(4), out var lastNum))
- nextPO = lastNum + 1;
+ var lastPO = await _dispatchDataService.GenerateNextPONumberAsync();
+ int nextPO = int.TryParse(lastPO, out var po) ? po : 1;
- var lastDSP = await _db.Dispatches
- .Where(d => d.DispatchNumber != null)
- .OrderByDescending(d => d.DispatchNumber)
- .Select(d => d.DispatchNumber)
- .FirstOrDefaultAsync();
- int nextDSP = 1;
- if (lastDSP != null && lastDSP.StartsWith("DSP-") && int.TryParse(lastDSP.Substring(4), out var lastDspNum))
- nextDSP = lastDspNum + 1;
+ var lastDSP = await _dispatchDataService.GenerateNextDispatchNumberAsync();
+ int nextDSP = lastDSP.StartsWith("DSP-") && int.TryParse(lastDSP.Substring(4), out var dspNum) ? dspNum : 1;
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
- var currentUser = userId != null ? await _db.Users.FindAsync(userId) : null;
+ var currentUser = userId != null ? await _userDataService.GetByIdAsync(userId) : null;
var senderName = currentUser != null ? (currentUser.FirstName + " " + currentUser.LastName).Trim() : "";
var dispatches = new List