diff --git a/ARCHITECTURE_AT_A_GLANCE.md b/ARCHITECTURE_AT_A_GLANCE.md
deleted file mode 100644
index 635b2d5..0000000
--- a/ARCHITECTURE_AT_A_GLANCE.md
+++ /dev/null
@@ -1,344 +0,0 @@
-# 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/CALENDAR_MODULE_COMPLETE.cs b/CALENDAR_MODULE_COMPLETE.cs
deleted file mode 100644
index 3e3bd12..0000000
--- a/CALENDAR_MODULE_COMPLETE.cs
+++ /dev/null
@@ -1,553 +0,0 @@
-/*
-===================================================================================
-CALENDAR/EVENTS MODULE - COMPLETE IMPLEMENTATION
-===================================================================================
-This file contains everything needed for the Calendar/Events module:
-1. Events Model (Database Entity)
-2. Event_DTO (for GET responses)
-3. EditEvent_DTO (for POST/PUT requests)
-4. CalendarController (API endpoints)
-5. SQL Script for database table creation and sample data
-
-Author: AI Agent
-Date: 2026-03-30
-===================================================================================
-*/
-
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
-using System.Threading.Tasks;
-using Api.SeaHavenIndustries.DTOs;
-using Data.SeaHavenIndustries;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Identity;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.EntityFrameworkCore;
-
-// ===================================================================================
-// SECTION 1: DATABASE MODEL (Data.SeaHavenIndustries/Models/Events.cs)
-// ===================================================================================
-
-namespace Data.SeaHavenIndustries
-{
- public class Events : FullAuditEntity
- {
- public string Title { get; set; } = null!;
- public string? Description { get; set; }
- public string? Location { get; set; }
-
- // Related Record (can link to Accounts, Contacts, Work Orders, etc.)
- public int? RecordID { get; set; }
- public string? RecordName { get; set; }
- public string? RecordTargetType { get; set; }
-
- // Reminder Settings
- public int ReminderMinutes { get; set; } = 15; // Default: 15 minutes before
-
- // Color for calendar display
- public string EventColor { get; set; } = "primary"; // primary, success, info, warning, danger
-
- // Schedule
- public DateTime StartDate { get; set; }
- public string? StartTime { get; set; } // e.g., "08:00 AM"
- public DateTime EndDate { get; set; }
- public string? EndTime { get; set; } // e.g., "08:30 AM"
- public bool AllDay { get; set; } = false;
- }
-}
-
-// ===================================================================================
-// SECTION 2: DTOs (Api.SeaHavenIndustries/DTOs/Event_DTO.cs)
-// ===================================================================================
-
-namespace Api.SeaHavenIndustries.DTOs
-{
- // DTO for GET responses
- public class Event_DTO
- {
- public int Id { get; set; }
- public string Title { get; set; } = null!;
- public string? Description { get; set; }
- public string? Location { get; set; }
-
- public int? RecordID { get; set; }
- public string? RecordName { get; set; }
- public string? RecordTargetType { get; set; }
-
- public int ReminderMinutes { get; set; }
- public string EventColor { get; set; } = null!;
-
- public DateTime StartDate { get; set; }
- public string? StartTime { get; set; }
- public DateTime EndDate { get; set; }
- public string? EndTime { get; set; }
- public bool AllDay { get; set; }
-
- public DateTime? CreatedDate { get; set; }
- public string? createdby { get; set; }
- }
-
- // DTO for POST/PUT requests (Create/Edit)
- public class EditEvent_DTO
- {
- public int? Id { get; set; } // Null for create, populated for edit
-
- [Required(ErrorMessage = "Please enter a title")]
- [StringLength(100, ErrorMessage = "Title cannot be longer than 100 characters")]
- public string Title { get; set; } = null!;
-
- public string? Description { get; set; }
-
- [StringLength(150, ErrorMessage = "Location cannot be longer than 150 characters")]
- public string? Location { get; set; }
-
- public int? RecordID { get; set; }
- public string? RecordName { get; set; }
- public string? RecordTargetType { get; set; }
-
- [Required(ErrorMessage = "The Remind Attendees field is required")]
- public int ReminderMinutes { get; set; } = 15;
-
- [Required(ErrorMessage = "The Select a Calendar Color field is required")]
- public string EventColor { get; set; } = "primary";
-
- [Required(ErrorMessage = "Please select a start date")]
- public DateTime StartDate { get; set; }
-
- [Required(ErrorMessage = "Please select a start time")]
- public string StartTime { get; set; } = null!;
-
- [Required(ErrorMessage = "Please select an end date")]
- public DateTime EndDate { get; set; }
-
- [Required(ErrorMessage = "Please select an end time")]
- public string EndTime { get; set; } = null!;
-
- public bool AllDay { get; set; } = false;
- }
-}
-
-// ===================================================================================
-// SECTION 3: CONTROLLER (Api.SeaHavenIndustries/Controllers/CalendarController.cs)
-// ===================================================================================
-
-namespace Api.SeaHavenIndustries.Controllers
-{
- //[Authorize] // Uncomment when authentication is needed
- [ApiController]
- [Route("api/Calendar")]
- public class CalendarController : Controller
- {
- private readonly UserManager _userManager;
- private readonly ApplicationDbContext _db;
-
- public CalendarController(UserManager userManager, ApplicationDbContext db)
- {
- _userManager = userManager;
- _db = db;
- }
-
- // ===================================================================================
- // CREATE EVENT
- // ===================================================================================
- [HttpPost]
- [Route("AddEvent")]
- public async Task AddEvent(EditEvent_DTO eventDto)
- {
- try
- {
- var model = new Events
- {
- Title = eventDto.Title,
- Description = eventDto.Description,
- Location = eventDto.Location,
- RecordID = eventDto.RecordID,
- RecordName = eventDto.RecordName,
- RecordTargetType = eventDto.RecordTargetType,
- ReminderMinutes = eventDto.ReminderMinutes,
- EventColor = eventDto.EventColor,
- StartDate = eventDto.StartDate,
- StartTime = eventDto.StartTime,
- EndDate = eventDto.EndDate,
- EndTime = eventDto.EndTime,
- AllDay = eventDto.AllDay,
- CreatedDate = DateTime.Now,
- IsDeleted = false
- };
-
- _db.Events.Add(model);
- await _db.SaveChangesAsync();
-
- return Ok(new DataResponse { Message = "Event Created Successfully", Status = "200" });
- }
- catch (Exception ex)
- {
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
- }
- }
-
- // ===================================================================================
- // UPDATE EVENT
- // ===================================================================================
- [HttpPost]
- [Route("EditEvent")]
- public async Task EditEvent(EditEvent_DTO eventDto)
- {
- try
- {
- if (!eventDto.Id.HasValue)
- {
- return BadRequest(new Response { Status = "Error", Message = "Event ID is required for editing" });
- }
-
- var model = await _db.Events.FindAsync(eventDto.Id.Value);
- if (model == null || model.IsDeleted == true)
- {
- return NotFound(new Response { Status = "Error", Message = "Event not found" });
- }
-
- model.Title = eventDto.Title;
- model.Description = eventDto.Description;
- model.Location = eventDto.Location;
- model.RecordID = eventDto.RecordID;
- model.RecordName = eventDto.RecordName;
- model.RecordTargetType = eventDto.RecordTargetType;
- model.ReminderMinutes = eventDto.ReminderMinutes;
- model.EventColor = eventDto.EventColor;
- model.StartDate = eventDto.StartDate;
- model.StartTime = eventDto.StartTime;
- model.EndDate = eventDto.EndDate;
- model.EndTime = eventDto.EndTime;
- model.AllDay = eventDto.AllDay;
- model.LastModificationTime = DateTime.Now;
-
- _db.Events.Update(model);
- await _db.SaveChangesAsync();
-
- return Ok(new DataResponse { Message = "Event Updated Successfully", Status = "200" });
- }
- catch (Exception ex)
- {
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
- }
- }
-
- // ===================================================================================
- // GET EVENT BY ID
- // ===================================================================================
- [HttpGet]
- [Route("GetEventById/{id}")]
- public async Task GetEventById(int id)
- {
- try
- {
- var model = await _db.Events
- .Where(e => e.Id == id && e.IsDeleted != true)
- .FirstOrDefaultAsync();
-
- if (model == null)
- {
- return NotFound(new Response { Status = "Error", Message = "Event not found" });
- }
-
- var dto = new Event_DTO
- {
- Id = model.Id,
- Title = model.Title,
- Description = model.Description,
- Location = model.Location,
- RecordID = model.RecordID,
- RecordName = model.RecordName,
- RecordTargetType = model.RecordTargetType,
- ReminderMinutes = model.ReminderMinutes,
- EventColor = model.EventColor,
- StartDate = model.StartDate,
- StartTime = model.StartTime,
- EndDate = model.EndDate,
- EndTime = model.EndTime,
- AllDay = model.AllDay,
- CreatedDate = model.CreatedDate,
- createdby = model.createdby
- };
-
- return Ok(new DataResponse { Message = "Success", Status = "200", Data = dto });
- }
- catch (Exception ex)
- {
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
- }
- }
-
-
- // ===================================================================================
- // DELETE EVENT
- // ===================================================================================
- [HttpDelete]
- [Route("DeleteEvent/{id}")]
- public async Task DeleteEvent(int id)
- {
- try
- {
- var model = await _db.Events.FindAsync(id);
- if (model == null)
- {
- return NotFound(new Response { Status = "Error", Message = "Event not found" });
- }
-
- // Soft delete
- model.IsDeleted = true;
- model.DeletionTime = DateTime.Now;
-
- _db.Events.Update(model);
- await _db.SaveChangesAsync();
-
- return Ok(new DataResponse { Message = "Event Deleted Successfully", Status = "200" });
- }
- catch (Exception ex)
- {
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
- }
- }
-
- // ===================================================================================
- // GET EVENTS LIST (WITH PAGINATION AND FILTERS)
- // ===================================================================================
- [HttpPost]
- [Route("GetEventList")]
- public IActionResult GetEventList(Pagination_DTO dto)
- {
- try
- {
- var query = _db.Events.Where(e => e.IsDeleted != true);
-
- // Apply search filter if provided
- if (!string.IsNullOrWhiteSpace(dto.Search))
- {
- query = query.Where(e =>
- e.Title.Contains(dto.Search) ||
- (e.Description != null && e.Description.Contains(dto.Search)) ||
- (e.Location != null && e.Location.Contains(dto.Search))
- );
- }
-
- // IMPORTANT: Calculate totalCount AFTER applying filters
- var totalCount = query.Count();
-
- // Apply pagination
- var events = query
- .OrderByDescending(e => e.StartDate)
- .ThenByDescending(e => e.CreatedDate)
- .Skip((dto.PageNumber - 1) * dto.PageSize)
- .Take(dto.PageSize)
- .Select(e => new Event_DTO
- {
- Id = e.Id,
- Title = e.Title,
- Description = e.Description,
- Location = e.Location,
- RecordID = e.RecordID,
- RecordName = e.RecordName,
- RecordTargetType = e.RecordTargetType,
- ReminderMinutes = e.ReminderMinutes,
- EventColor = e.EventColor,
- StartDate = e.StartDate,
- StartTime = e.StartTime,
- EndDate = e.EndDate,
- EndTime = e.EndTime,
- AllDay = e.AllDay,
- CreatedDate = e.CreatedDate,
- createdby = e.createdby
- })
- .ToList();
-
- return Ok(new DataResponse
- {
- Message = "Success",
- Status = "200",
- Data = new { rows = events, totalCount }
- });
- }
- catch (Exception ex)
- {
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
- }
- }
-
- // ===================================================================================
- // GET EVENTS FOR CALENDAR (Date Range Filter)
- // ===================================================================================
- [HttpGet]
- [Route("GetEventsForCalendar")]
- public IActionResult GetEventsForCalendar([FromQuery] DateTime? start, [FromQuery] DateTime? end)
- {
- try
- {
- var query = _db.Events.Where(e => e.IsDeleted != true);
-
- // Filter by date range if provided
- if (start.HasValue && end.HasValue)
- {
- query = query.Where(e =>
- (e.StartDate >= start.Value && e.StartDate <= end.Value) ||
- (e.EndDate >= start.Value && e.EndDate <= end.Value) ||
- (e.StartDate <= start.Value && e.EndDate >= end.Value)
- );
- }
-
- var events = query
- .Select(e => new Event_DTO
- {
- Id = e.Id,
- Title = e.Title,
- Description = e.Description,
- Location = e.Location,
- RecordID = e.RecordID,
- RecordName = e.RecordName,
- RecordTargetType = e.RecordTargetType,
- ReminderMinutes = e.ReminderMinutes,
- EventColor = e.EventColor,
- StartDate = e.StartDate,
- StartTime = e.StartTime,
- EndDate = e.EndDate,
- EndTime = e.EndTime,
- AllDay = e.AllDay,
- CreatedDate = e.CreatedDate,
- createdby = e.createdby
- })
- .ToList();
-
- return Ok(new DataResponse { Message = "Success", Status = "200", Data = events });
- }
- catch (Exception ex)
- {
- return BadRequest(new Response { Status = "Error", Message = ex.Message });
- }
- }
- }
-}
-
-/*
-===================================================================================
-SECTION 4: DATABASE SETUP
-===================================================================================
-
-STEP 1: Add Events DbSet to ApplicationDbContext.cs
--------------------------------------------------------
-Add this line in the ApplicationDbContext class (around line 48):
-
- public DbSet Events { get; set; }
-
-
-STEP 2: Create Database Migration
--------------------------------------------------------
-Run these commands in the terminal from the Api.SeaHavenIndustries project directory:
-
- dotnet ef migrations add AddEventsTable --project ../Data.SeaHavenIndustries
- dotnet ef database update
-
-
-STEP 3: SQL Script for Manual Table Creation (Alternative to migrations)
--------------------------------------------------------
-*/
-
--- SQL SCRIPT TO CREATE EVENTS TABLE
-/*
-CREATE TABLE Events (
- Id INT PRIMARY KEY IDENTITY(1,1),
- Title NVARCHAR(100) NOT NULL,
- Description NVARCHAR(MAX) NULL,
- Location NVARCHAR(150) NULL,
-
- RecordID INT NULL,
- RecordName NVARCHAR(255) NULL,
- RecordTargetType NVARCHAR(50) NULL,
-
- ReminderMinutes INT NOT NULL DEFAULT 15,
- EventColor NVARCHAR(20) NOT NULL DEFAULT 'primary',
-
- StartDate DATETIME NOT NULL,
- StartTime NVARCHAR(10) NULL,
- EndDate DATETIME NOT NULL,
- EndTime NVARCHAR(10) NULL,
- AllDay BIT NOT NULL DEFAULT 0,
-
- -- Audit fields
- IsDeleted BIT NULL,
- createdby NVARCHAR(450) NULL,
- DeleterUserId NVARCHAR(450) NULL,
- DeletionTime DATETIME NULL,
- CreatedDate DATETIME NULL,
- LastModificationTime DATETIME NULL,
- LastModifierUserId INT NULL
-);
-
--- Insert sample data
-INSERT INTO Events (Title, Description, Location, StartDate, StartTime, EndDate, EndTime, AllDay, EventColor, ReminderMinutes, CreatedDate, IsDeleted)
-VALUES
-('Team Meeting', 'Weekly team sync', 'Conference Room A', '2026-04-01', '10:00 AM', '2026-04-01', '11:00 AM', 0, 'primary', 15, GETDATE(), 0),
-('Client Presentation', 'Q1 Results Review with ABC Corp', 'Virtual - Zoom', '2026-04-03', '02:00 PM', '2026-04-03', '03:30 PM', 0, 'success', 30, GETDATE(), 0),
-('Project Deadline', 'Final delivery for Project Phoenix', NULL, '2026-04-05', NULL, '2026-04-05', NULL, 1, 'danger', 1440, GETDATE(), 0),
-('Training Session', 'New system training for all employees', 'Main Office', '2026-04-08', '09:00 AM', '2026-04-08', '12:00 PM', 0, 'info', 60, GETDATE(), 0),
-('Equipment Maintenance', 'Scheduled maintenance for all assets', 'Warehouse', '2026-04-10', '08:00 AM', '2026-04-10', '05:00 PM', 0, 'warning', 120, GETDATE(), 0);
-*/
-
-/*
-===================================================================================
-SECTION 5: IMPLEMENTATION CHECKLIST
-===================================================================================
-
-BACKEND SETUP:
---------------
-1. ✓ Create Events.cs in Data.SeaHavenIndustries/Models/
-2. ✓ Create Event_DTO.cs in Api.SeaHavenIndustries/DTOs/
-3. ✓ Create EditEvent_DTO.cs in Api.SeaHavenIndustries/DTOs/
-4. ✓ Create CalendarController.cs in Api.SeaHavenIndustries/Controllers/
-5. ☐ Add DbSet to ApplicationDbContext.cs
-6. ☐ Run migrations (or execute SQL script)
-7. ☐ Test all endpoints using Postman/Swagger
-
-FRONTEND SETUP (Next Step):
----------------------------
-1. ☐ Create src/pages/calendar/CalendarPage.js (List view with FullCalendar)
-2. ☐ Create src/pages/calendar/EventFormPage.js (Create/Edit form)
-3. ☐ Create src/pages/calendar/api.js (API calls)
-4. ☐ Add routes to App.js
-5. ☐ Install FullCalendar: npm install @fullcalendar/react @fullcalendar/daygrid @fullcalendar/timegrid @fullcalendar/interaction
-
-API ENDPOINTS SUMMARY:
----------------------
-POST /api/Calendar/AddEvent - Create new event
-POST /api/Calendar/EditEvent - Update existing event
-GET /api/Calendar/GetEventById/{id} - Get single event by ID
-DELETE /api/Calendar/DeleteEvent/{id} - Delete event (soft delete)
-POST /api/Calendar/GetEventList - Get paginated list with search
-GET /api/Calendar/GetEventsForCalendar - Get events for calendar view (with date range)
-
-KEY FEATURES IMPLEMENTED:
--------------------------
-✓ CRUD operations (Create, Read, Update, Delete)
-✓ Pagination with search filter
-✓ Date range filtering for calendar view
-✓ Soft delete (IsDeleted flag)
-✓ Related record linking (RecordID, RecordName, RecordTargetType)
-✓ Color coding for calendar display
-✓ All-day event support
-✓ Reminder settings
-✓ Proper error handling
-✓ Consistent with existing controller patterns (AssetController)
-✓ Fixed pagination bug (totalCount calculated AFTER filters)
-
-NOTES:
-------
-- EventColor values: 'primary', 'success', 'info', 'warning', 'danger'
-- ReminderMinutes: 0=Never, 5, 10, 15, 30, 60, 120, 240, 360, 720, 1440 (1 day)
-- StartTime/EndTime format: "08:00 AM", "02:30 PM", etc.
-- AllDay: When true, time fields can be null/ignored
-- RecordTargetType: Can be "Account", "Contact", "WorkOrder", "FollowUp", etc.
-
-===================================================================================
-END OF FILE
-===================================================================================
-*/
diff --git a/DI_ASSEMBLY_SCANNING_GUIDE.md b/DI_ASSEMBLY_SCANNING_GUIDE.md
deleted file mode 100644
index b678c9c..0000000
--- a/DI_ASSEMBLY_SCANNING_GUIDE.md
+++ /dev/null
@@ -1,237 +0,0 @@
-# Dependency Injection - Assembly Scanning Guide
-
-## 🎯 Overview
-
-This project uses **assembly scanning** to automatically register services instead of manual registration. This means you don't need to add each service to `Program.cs` individually.
-
----
-
-## 🔍 How It Works
-
-### In `Program.cs`
-
-```csharp
-// These two lines register ALL services automatically
-builder.Services.AddDataServices(); // Scans SeaHaven.DataServices assembly
-builder.Services.AddBusinessServices(); // Scans SeaHaven.Services assembly + Validators
-```
-
-That's it! No need for this:
-```csharp
-// ❌ DON'T DO THIS - It's automatic now!
-builder.Services.AddScoped();
-builder.Services.AddScoped();
-builder.Services.AddScoped();
-// ... hundreds of lines
-```
-
----
-
-## 📦 DataServicesModule
-
-**Location**: `SeaHaven.DataServices/DependencyInjection/DataServicesModule.cs`
-
-```csharp
-public static IServiceCollection AddDataServices(this IServiceCollection services)
-{
- var assembly = Assembly.GetExecutingAssembly();
-
- // Find all non-abstract classes in the assembly
- var allClasses = assembly.GetTypes()
- .Where(t => t.IsClass && !t.IsAbstract && !t.IsGenericType)
- .ToList();
-
- foreach (var implementationType in allClasses)
- {
- // Find interface matching pattern: I{ClassName}
- var defaultInterface = implementationType.GetInterfaces()
- .FirstOrDefault(i => i.Name == $"I{implementationType.Name}");
-
- if (defaultInterface != null)
- {
- // Register as Scoped
- services.AddScoped(defaultInterface, implementationType);
- }
- }
-
- return services;
-}
-```
-
-### What It Does
-
-1. Scans entire `SeaHaven.DataServices` assembly
-2. Finds all concrete classes (e.g., `ContactDataService`, `EmployeeDataService`)
-3. For each class, looks for an interface named `I{ClassName}`
-4. If found, registers the pair as **Scoped** service
-
-### Example
-
-If you create:
-- `IProductDataService` (interface)
-- `ProductDataService` (class implementing `IProductDataService`)
-
-Then `IProductDataService → ProductDataService` is **automatically registered** as Scoped.
-
----
-
-## 💼 ServicesModule
-
-**Location**: `SeaHaven.Services/DependencyInjection/ServicesModule.cs`
-
-```csharp
-public static IServiceCollection AddBusinessServices(this IServiceCollection services)
-{
- var assembly = Assembly.GetExecutingAssembly();
-
- var allClasses = assembly.GetTypes()
- .Where(t => t.IsClass && !t.IsAbstract && !t.IsGenericType)
- .ToList();
-
- foreach (var implementationType in allClasses)
- {
- var defaultInterface = implementationType.GetInterfaces()
- .FirstOrDefault(i => i.Name == $"I{implementationType.Name}");
-
- if (defaultInterface != null)
- {
- services.AddScoped(defaultInterface, implementationType);
- }
- }
-
- // ALSO auto-register all FluentValidation validators
- services.AddValidatorsFromAssembly(assembly);
-
- return services;
-}
-```
-
-### What It Does
-
-1. Scans entire `SeaHaven.Services` assembly
-2. Registers all services using naming convention (same as DataServices)
-3. **BONUS**: Automatically registers ALL FluentValidation validators in the assembly
-
-### Example
-
-If you create:
-- `IProductService` (interface)
-- `ProductService` (class)
-- `CreateProductDTOValidator` (FluentValidation validator)
-
-Then **all three** are automatically registered.
-
----
-
-## ⚠️ CRITICAL: Naming Convention
-
-For auto-registration to work, you MUST follow this pattern:
-
-### ✅ CORRECT
-
-| Interface | Implementation | Result |
-|-----------|---------------|--------|
-| `IContactService` | `ContactService` | ✅ Registered as Scoped |
-| `IContactDataService` | `ContactDataService` | ✅ Registered as Scoped |
-| `IProductService` | `ProductService` | ✅ Registered as Scoped |
-| `IEmployeeService` | `EmployeeService` | ✅ Registered as Scoped |
-
-### ❌ WRONG
-
-| Interface | Implementation | Result |
-|-----------|---------------|--------|
-| `IProductService` | `ProductServiceImpl` | ❌ NOT registered - names don't match |
-| `IContactRepository` | `ContactService` | ❌ NOT registered - names don't match |
-| `ISomething` | `SomethingElse` | ❌ NOT registered - names don't match |
-
----
-
-## 📋 Rules
-
-1. **Interface naming**: `I{ClassName}`
-2. **Class naming**: `{ClassName}`
-3. **Example**:
- - Interface: `IContactService`
- - Class: `ContactService` (NOT `ContactServiceImpl`, `ContactServiceImplementation`, etc.)
-
----
-
-## 🚀 Benefits
-
-✅ **Less boilerplate** - No need to add each service to `Program.cs`
-✅ **Auto-discovery** - New services are automatically registered
-✅ **Consistent** - All services registered the same way
-✅ **FluentValidation included** - Validators work automatically
-✅ **Cleaner Program.cs** - Only 2 lines instead of 50+
-
----
-
-## 🎯 How to Add a New Service
-
-### Old Way (Manual)
-```csharp
-// 1. Create interface and class
-public interface IProductService { }
-public class ProductService : IProductService { }
-
-// 2. Manually add to Program.cs
-builder.Services.AddScoped(); // ❌ DON'T DO THIS
-```
-
-### New Way (Automatic)
-```csharp
-// 1. Create interface and class following naming convention
-public interface IProductService { }
-public class ProductService : IProductService { }
-
-// 2. Done! It's automatically registered ✅
-```
-
----
-
-## 🔧 Lifecycle
-
-All services are registered as **Scoped**:
-- New instance per HTTP request
-- Shared within the same request
-- Disposed at end of request
-
----
-
-## 📚 Example Flow
-
-1. You create `IProductDataService` and `ProductDataService` in `SeaHaven.DataServices`
-2. You create `IProductService` and `ProductService` in `SeaHaven.Services`
-3. You create `CreateProductDTOValidator` in `SeaHaven.Services/Validation`
-4. Application starts → `AddDataServices()` and `AddBusinessServices()` run
-5. Assembly scanner finds your services and validator
-6. All registered automatically as Scoped
-7. You inject `IProductService` in controller → works immediately ✅
-
----
-
-## 🐛 Troubleshooting
-
-### Service not being injected?
-
-Check:
-1. ✅ Is the interface named `I{ClassName}`?
-2. ✅ Is the class named `{ClassName}`?
-3. ✅ Is the class in the correct assembly?
-4. ✅ Is the class public and concrete (not abstract)?
-5. ✅ Does the class implement the interface?
-
-### Validator not working?
-
-Check:
-1. ✅ Is the validator in `SeaHaven.Services` assembly?
-2. ✅ Does it inherit from `AbstractValidator`?
-3. ✅ Is it public and concrete?
-
----
-
-## 📖 See Also
-
-- **Full Architecture Guide**: `BACKEND_ARCHITECTURE.md`
-- **Quick Start Guide**: `QUICK_START_GUIDE.md`
-- **At a Glance**: `ARCHITECTURE_AT_A_GLANCE.md`
diff --git a/FIX_LOCATIONS_SCHEMA.sql b/FIX_LOCATIONS_SCHEMA.sql
deleted file mode 100644
index 042c7a8..0000000
--- a/FIX_LOCATIONS_SCHEMA.sql
+++ /dev/null
@@ -1,109 +0,0 @@
--- SQL Script to add missing columns to Locations table
--- Run this in SQL Server Management Studio
-
-USE [db_a7141e_seahavenindustry]
-GO
-
--- Check and add AccountId column
-IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'AccountId')
-BEGIN
- ALTER TABLE [dbo].[Locations] ADD [AccountId] int NULL;
- PRINT 'Added AccountId column';
-END
-ELSE
- PRINT 'AccountId column already exists';
-
--- Check and add Address column
-IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'Address')
-BEGIN
- ALTER TABLE [dbo].[Locations] ADD [Address] nvarchar(max) NULL;
- PRINT 'Added Address column';
-END
-ELSE
- PRINT 'Address column already exists';
-
--- Check and add City column
-IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'City')
-BEGIN
- ALTER TABLE [dbo].[Locations] ADD [City] nvarchar(max) NULL;
- PRINT 'Added City column';
-END
-ELSE
- PRINT 'City column already exists';
-
--- Check and add Contact column
-IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'Contact')
-BEGIN
- ALTER TABLE [dbo].[Locations] ADD [Contact] nvarchar(max) NULL;
- PRINT 'Added Contact column';
-END
-ELSE
- PRINT 'Contact column already exists';
-
--- Check and add ContactEmail column
-IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'ContactEmail')
-BEGIN
- ALTER TABLE [dbo].[Locations] ADD [ContactEmail] nvarchar(max) NULL;
- PRINT 'Added ContactEmail column';
-END
-ELSE
- PRINT 'ContactEmail column already exists';
-
--- Check and add Phone column
-IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'Phone')
-BEGIN
- ALTER TABLE [dbo].[Locations] ADD [Phone] nvarchar(max) NULL;
- PRINT 'Added Phone column';
-END
-ELSE
- PRINT 'Phone column already exists';
-
--- Check and add State column
-IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'State')
-BEGIN
- ALTER TABLE [dbo].[Locations] ADD [State] nvarchar(max) NULL;
- PRINT 'Added State column';
-END
-ELSE
- PRINT 'State column already exists';
-
--- Check and add Status column
-IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'Status')
-BEGIN
- ALTER TABLE [dbo].[Locations] ADD [Status] nvarchar(max) NULL;
- PRINT 'Added Status column';
-END
-ELSE
- PRINT 'Status column already exists';
-
--- Check and add ZipCode column
-IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'Locations' AND COLUMN_NAME = 'ZipCode')
-BEGIN
- ALTER TABLE [dbo].[Locations] ADD [ZipCode] nvarchar(max) NULL;
- PRINT 'Added ZipCode column';
-END
-ELSE
- PRINT 'ZipCode column already exists';
-
--- Check and add foreign key index
-IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = 'IX_Locations_AccountId' AND object_id = OBJECT_ID('dbo.Locations'))
-BEGIN
- CREATE NONCLUSTERED INDEX [IX_Locations_AccountId] ON [dbo].[Locations] ([AccountId] ASC);
- PRINT 'Added IX_Locations_AccountId index';
-END
-ELSE
- PRINT 'IX_Locations_AccountId index already exists';
-
--- Check and add foreign key constraint if Accounts table exists
-IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'Accounts')
- AND NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE name = 'FK_Locations_Accounts_AccountId')
-BEGIN
- ALTER TABLE [dbo].[Locations] WITH CHECK ADD CONSTRAINT [FK_Locations_Accounts_AccountId]
- FOREIGN KEY([AccountId]) REFERENCES [dbo].[Accounts] ([Id]);
- PRINT 'Added FK_Locations_Accounts_AccountId foreign key';
-END
-ELSE
- PRINT 'FK_Locations_Accounts_AccountId foreign key already exists or Accounts table does not exist';
-
-PRINT 'Schema fix completed!';
-GO
diff --git a/QUICK_START_GUIDE.md b/QUICK_START_GUIDE.md
deleted file mode 100644
index c116639..0000000
--- a/QUICK_START_GUIDE.md
+++ /dev/null
@@ -1,515 +0,0 @@
-# Backend Quick Start Guide
-
-## 🚀 Adding a New Feature (Step-by-Step)
-
-### Example: Adding a "Product" Module
-
----
-
-## Step 1: Create Entity (Data Layer)
-
-**File**: `Data.SeaHavenIndustries/Models/Product.cs`
-
-```csharp
-public class Product
-{
- public int Id { get; set; }
- public string Name { get; set; } = string.Empty;
- public string? Description { get; set; }
- public decimal Price { get; set; }
- public bool IsActive { get; set; }
-
- // Audit fields (REQUIRED)
- public string CreatedBy { get; set; } = string.Empty;
- public DateTime CreatedDate { get; set; }
- public string ModifiedBy { get; set; } = string.Empty;
- public DateTime ModifiedDate { get; set; }
-}
-```
-
-**Add to DbContext**: `Data.SeaHavenIndustries/ApplicationDbContext.cs`
-
-```csharp
-public DbSet Products { get; set; }
-```
-
----
-
-## Step 2: Create Migration
-
-```bash
-cd backend/shoc-backend
-dotnet ef migrations add AddProductTable --project Data.SeaHavenIndustries --startup-project Api.SeaHavenIndustries
-dotnet ef database update --project Data.SeaHavenIndustries --startup-project Api.SeaHavenIndustries
-```
-
----
-
-## Step 3: Create DTOs
-
-### 3A. Data Layer DTOs (`SeaHaven.DataServices/DTOs/`)
-
-**CreateProductDataDTO.cs**
-```csharp
-public class CreateProductDataDTO
-{
- public string Name { get; set; } = string.Empty;
- public string? Description { get; set; }
- public decimal Price { get; set; }
-}
-```
-
-**ProductDataDTO.cs**
-```csharp
-public class ProductDataDTO
-{
- public int Id { get; set; }
- public string Name { get; set; } = string.Empty;
- public string? Description { get; set; }
- public decimal Price { get; set; }
- public bool IsActive { get; set; }
-}
-```
-
-### 3B. Service Layer DTOs (`SeaHaven.Services/DTOs/`)
-
-**CreateProductDTO.cs**
-```csharp
-public class CreateProductDTO
-{
- public string Name { get; set; } = string.Empty;
- public string? Description { get; set; }
- public decimal Price { get; set; }
-}
-```
-
-**ProductDTO.cs**
-```csharp
-public class ProductDTO
-{
- public int Id { get; set; }
- public string Name { get; set; } = string.Empty;
- public string? Description { get; set; }
- public decimal Price { get; set; }
- public bool IsActive { get; set; }
-}
-```
-
-### 3C. API Layer DTOs (`Api.SeaHavenIndustries/DTOs/`)
-
-**Product_DTO.cs**
-```csharp
-public class Product_DTO
-{
- public string Name { get; set; } = string.Empty;
- public string? Description { get; set; }
- public decimal Price { get; set; }
-}
-```
-
----
-
-## Step 4: Create FluentValidation Validator
-
-**File**: `SeaHaven.Services/Validators/CreateProductDTOValidator.cs`
-
-```csharp
-using FluentValidation;
-using SeaHaven.Services.DTOs;
-
-public class CreateProductDTOValidator : AbstractValidator
-{
- public CreateProductDTOValidator()
- {
- RuleFor(x => x.Name)
- .NotEmpty().WithMessage("Product name is required")
- .MaximumLength(100).WithMessage("Name cannot exceed 100 characters");
-
- RuleFor(x => x.Price)
- .GreaterThan(0).WithMessage("Price must be greater than 0");
- }
-}
-```
-
----
-
-## Step 5: Create DataService
-
-### 5A. Interface (`SeaHaven.DataServices/Interfaces/IProductDataService.cs`)
-
-```csharp
-public interface IProductDataService
-{
- Task CreateAsync(CreateProductDataDTO dto, string userId);
- Task UpdateAsync(int id, UpdateProductDataDTO dto, string userId);
- Task DeleteAsync(int id, string userId);
- Task GetByIdAsync(int id);
- Task> GetPagedAsync(int page, int pageSize, string? search);
-}
-```
-
-### 5B. Implementation (`SeaHaven.DataServices/Implementation/ProductDataService.cs`)
-
-```csharp
-using Data.SeaHavenIndustries;
-using Data.SeaHavenIndustries.Models;
-using Microsoft.EntityFrameworkCore;
-
-public class ProductDataService : IProductDataService
-{
- private readonly ApplicationDbContext _db;
-
- public ProductDataService(ApplicationDbContext db)
- {
- _db = db;
- }
-
- public async Task CreateAsync(CreateProductDataDTO dto, string userId)
- {
- var product = new Product
- {
- Name = dto.Name,
- Description = dto.Description,
- Price = dto.Price,
- IsActive = true,
- CreatedBy = userId,
- CreatedDate = DateTime.UtcNow,
- ModifiedBy = userId,
- ModifiedDate = DateTime.UtcNow
- };
-
- _db.Products.Add(product);
- await _db.SaveChangesAsync();
-
- return new ProductDataDTO
- {
- Id = product.Id,
- Name = product.Name,
- Description = product.Description,
- Price = product.Price,
- IsActive = product.IsActive
- };
- }
-
- public async Task GetByIdAsync(int id)
- {
- var product = await _db.Products.FindAsync(id);
- if (product == null) return null;
-
- return new ProductDataDTO
- {
- Id = product.Id,
- Name = product.Name,
- Description = product.Description,
- Price = product.Price,
- IsActive = product.IsActive
- };
- }
-
- // Implement other methods...
-}
-```
-
----
-
-## Step 6: Create Service
-
-### 6A. Interface (`SeaHaven.Services/Interfaces/IProductService.cs`)
-
-```csharp
-public interface IProductService
-{
- Task CreateProductAsync(CreateProductDTO dto, string userId);
- Task UpdateProductAsync(int id, UpdateProductDTO dto, string userId);
- Task DeleteProductAsync(int id, string userId);
- Task GetProductByIdAsync(int id);
- Task> GetProductsPagedAsync(int page, int pageSize, string? search);
-}
-```
-
-### 6B. Implementation (`SeaHaven.Services/Implementation/ProductService.cs`)
-
-```csharp
-using FluentValidation;
-using SeaHaven.DataServices.Interfaces;
-using SeaHaven.Services.DTOs;
-using SeaHaven.Services.Interfaces;
-
-public class ProductService : IProductService
-{
- private readonly IProductDataService _dataService;
- private readonly IValidator _createValidator;
-
- public ProductService(
- IProductDataService dataService,
- IValidator createValidator)
- {
- _dataService = dataService;
- _createValidator = createValidator;
- }
-
- public async Task CreateProductAsync(CreateProductDTO dto, string userId)
- {
- // Validate
- var validationResult = await _createValidator.ValidateAsync(dto);
- if (!validationResult.IsValid)
- {
- throw new ValidationException(validationResult.Errors);
- }
-
- // Map to Data DTO
- var dataDto = new CreateProductDataDTO
- {
- Name = dto.Name,
- Description = dto.Description,
- Price = dto.Price
- };
-
- // Call DataService
- var created = await _dataService.CreateAsync(dataDto, userId);
-
- // Map to Service DTO
- return new ProductDTO
- {
- Id = created.Id,
- Name = created.Name,
- Description = created.Description,
- Price = created.Price,
- IsActive = created.IsActive
- };
- }
-
- public async Task GetProductByIdAsync(int id)
- {
- var product = await _dataService.GetByIdAsync(id);
- if (product == null)
- throw new KeyNotFoundException($"Product with ID {id} not found");
-
- return new ProductDTO
- {
- Id = product.Id,
- Name = product.Name,
- Description = product.Description,
- Price = product.Price,
- IsActive = product.IsActive
- };
- }
-
- // Implement other methods...
-}
-```
-
----
-
-## Step 7: Create Controller
-
-**File**: `Api.SeaHavenIndustries/Controllers/ProductController.cs`
-
-```csharp
-using Api.SeaHavenIndustries.DTOs;
-using FluentValidation;
-using Microsoft.AspNetCore.Mvc;
-using SeaHaven.Services.Interfaces;
-using System.Security.Claims;
-
-[ApiController]
-[Route("api/[controller]")]
-public class ProductController : Controller
-{
- private readonly IProductService _productService;
-
- public ProductController(IProductService productService)
- {
- _productService = productService;
- }
-
- [HttpPost("Create")]
- public async Task Create([FromBody] Product_DTO dto)
- {
- try
- {
- var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
- if (userId == null)
- return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
-
- var serviceDto = new CreateProductDTO
- {
- Name = dto.Name,
- Description = dto.Description,
- Price = dto.Price
- };
-
- await _productService.CreateProductAsync(serviceDto, userId);
-
- return Ok(new Response { Status = "Success", Message = "Product created 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 StatusCode(500, new Response { Status = "Error", Message = ex.Message });
- }
- }
-
- [HttpGet("GetById")]
- public async Task GetById(int id)
- {
- try
- {
- var product = await _productService.GetProductByIdAsync(id);
- return Ok(product);
- }
- catch (KeyNotFoundException)
- {
- return NotFound(new Response { Status = "Error", Message = "Product not found" });
- }
- catch (Exception ex)
- {
- return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
- }
- }
-
- // Add other endpoints...
-}
-```
-
----
-
-## Step 8: Services Auto-Registered ✅
-
-**No action needed!** Services are automatically registered by assembly scanning.
-
-If you followed naming conventions (`IProductService` → `ProductService`), your services are already registered.
-
-**How?** These lines in `Program.cs` scan assemblies:
-```csharp
-builder.Services.AddDataServices(); // Scans SeaHaven.DataServices
-builder.Services.AddBusinessServices(); // Scans SeaHaven.Services + Validators
-```
-
----
-
-## Step 9: Test
-
-```bash
-# Build
-dotnet build
-
-# Run
-dotnet run --project Api.SeaHavenIndustries
-
-# Test endpoint
-curl -X POST "http://localhost:5141/api/Product/Create" \
- -H "Content-Type: application/json" \
- -d '{"name":"Test Product","description":"Test","price":99.99}'
-```
-
----
-
-## ✅ Checklist
-
-- [ ] Entity created with audit fields
-- [ ] DbSet added to DbContext
-- [ ] Migration created and applied
-- [ ] Data DTOs created
-- [ ] Service DTOs created
-- [ ] API DTOs created
-- [ ] FluentValidation validator created
-- [ ] DataService interface created
-- [ ] DataService implementation created
-- [ ] Service interface created
-- [ ] Service implementation created
-- [ ] Controller created
-- [ ] Services registered in DI container
-- [ ] Exception handling implemented
-- [ ] User ID authentication added
-- [ ] Build succeeds
-- [ ] Endpoints tested
-
----
-
-## 🎯 Common Patterns
-
-### Get List with Pagination
-```csharp
-[HttpGet("GetList")]
-public async Task GetList(string? search = "", int page = 1, int pageSize = 10)
-{
- var result = await _productService.GetProductsPagedAsync(page, pageSize, search);
- return Ok(new Pagination_DTO
- {
- Data = result.Items,
- PageNumber = page,
- PageSize = pageSize,
- TotalCount = result.TotalCount,
- TotalPages = (int)Math.Ceiling(result.TotalCount / (double)pageSize)
- });
-}
-```
-
-### Update
-```csharp
-[HttpPost("Update")]
-public async Task Update([FromBody] EditProduct_DTO dto)
-{
- try
- {
- var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
- if (userId == null)
- return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
-
- await _productService.UpdateProductAsync(dto.Id, dto.ToServiceDTO(), userId);
- return Ok(new Response { Status = "Success", Message = "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 = "Not found" });
- }
- catch (Exception ex)
- {
- return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
- }
-}
-```
-
-### Delete
-```csharp
-[HttpPost("Delete")]
-public async Task Delete(int id)
-{
- try
- {
- var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
- if (userId == null)
- return Unauthorized(new Response { Status = "Error", Message = "User not authenticated" });
-
- await _productService.DeleteProductAsync(id, userId);
- return Ok(new Response { Status = "Success", Message = "Deleted" });
- }
- catch (KeyNotFoundException)
- {
- return NotFound(new Response { Status = "Error", Message = "Not found" });
- }
- catch (Exception ex)
- {
- return StatusCode(500, new Response { Status = "Error", Message = ex.Message });
- }
-}
-```
-
----
-
-## 📚 Reference Implementations
-
-For complete working examples, see:
-- `ContactController`, `ContactService`, `ContactDataService`
-- `EmployeeController`, `EmployeeService`, `EmployeeDataService`
-- `AssetController`, `AssetService`, `AssetDataService`