Added the API to get list of jobs
This commit is contained in:
parent
6e945cf6c1
commit
4a0144c23b
8461
Marco.Pms.DataAccess/Migrations/20251113051300_Removed_Assignee_From_JobTicket_Table.Designer.cs
generated
Normal file
8461
Marco.Pms.DataAccess/Migrations/20251113051300_Removed_Assignee_From_JobTicket_Table.Designer.cs
generated
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Removed_Assignee_From_JobTicket_Table : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_JobTickets_Employees_AssigneeId",
|
||||
table: "JobTickets");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_JobTickets_AssigneeId",
|
||||
table: "JobTickets");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AssigneeId",
|
||||
table: "JobTickets");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "AssigneeId",
|
||||
table: "JobTickets",
|
||||
type: "char(36)",
|
||||
nullable: true,
|
||||
collation: "ascii_general_ci");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_JobTickets_AssigneeId",
|
||||
table: "JobTickets",
|
||||
column: "AssigneeId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_JobTickets_Employees_AssigneeId",
|
||||
table: "JobTickets",
|
||||
column: "AssigneeId",
|
||||
principalTable: "Employees",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5291,9 +5291,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<Guid?>("AssigneeId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
@ -5334,8 +5331,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AssigneeId");
|
||||
|
||||
b.HasIndex("CreatedById");
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
@ -8086,10 +8081,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.ServiceProject.JobTicket", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "Assignee")
|
||||
.WithMany()
|
||||
.HasForeignKey("AssigneeId");
|
||||
|
||||
b.HasOne("Marco.Pms.Model.Employees.Employee", "CreatedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedById")
|
||||
@ -8118,8 +8109,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
.WithMany()
|
||||
.HasForeignKey("UpdatedById");
|
||||
|
||||
b.Navigation("Assignee");
|
||||
|
||||
b.Navigation("CreatedBy");
|
||||
|
||||
b.Navigation("Project");
|
||||
|
||||
@ -15,11 +15,6 @@ namespace Marco.Pms.Model.ServiceProject
|
||||
[ValidateNever]
|
||||
[ForeignKey("ProjectId")]
|
||||
public ServiceProject? Project { get; set; }
|
||||
public Guid? AssigneeId { get; set; }
|
||||
|
||||
[ValidateNever]
|
||||
[ForeignKey("AssigneeId")]
|
||||
public Employee? Assignee { get; set; }
|
||||
public Guid StatusId { get; set; }
|
||||
|
||||
[ValidateNever]
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
using Marco.Pms.Model.Dtos.ServiceProject;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using Marco.Pms.Model.ViewModels.ServiceProject;
|
||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||
using MarcoBMS.Services.Helpers;
|
||||
@ -35,11 +34,6 @@ namespace Marco.Pms.Services.Controllers
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetServiceProjectList([FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
|
||||
}
|
||||
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.GetServiceProjectListAsync(pageNumber, pageSize, loggedInEmploee, tenantId);
|
||||
return StatusCode(response.StatusCode, response);
|
||||
@ -49,11 +43,6 @@ namespace Marco.Pms.Services.Controllers
|
||||
[HttpGet("details/{id}")]
|
||||
public async Task<IActionResult> GetServiceProjectDetails(Guid id)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
|
||||
}
|
||||
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.GetServiceProjectDetailsAsync(id, loggedInEmploee, tenantId);
|
||||
return StatusCode(response.StatusCode, response);
|
||||
@ -63,11 +52,6 @@ namespace Marco.Pms.Services.Controllers
|
||||
[HttpPost("create")]
|
||||
public async Task<IActionResult> CreateProject([FromBody] ServiceProjectDto serviceProject)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
|
||||
}
|
||||
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.CreateServiceProjectAsync(serviceProject, loggedInEmploee, tenantId);
|
||||
if (response.Success)
|
||||
@ -83,11 +67,6 @@ namespace Marco.Pms.Services.Controllers
|
||||
[HttpPut("edit/{id}")]
|
||||
public async Task<IActionResult> UpdateServicecProject(Guid id, [FromBody] ServiceProjectDto serviceProject)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
|
||||
}
|
||||
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.UpdateServiceProjectAsync(id, serviceProject, loggedInEmploee, tenantId);
|
||||
if (response.Success)
|
||||
@ -102,11 +81,6 @@ namespace Marco.Pms.Services.Controllers
|
||||
[HttpDelete("delete/{id}")]
|
||||
public async Task<IActionResult> DeActivateServiceProject(Guid id, bool isActive = false)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
|
||||
}
|
||||
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.DeActivateServiceProjectAsync(id, isActive, loggedInEmploee, tenantId);
|
||||
if (response.Success)
|
||||
@ -120,14 +94,18 @@ namespace Marco.Pms.Services.Controllers
|
||||
|
||||
#region =================================================================== Job Tickets Functions ===================================================================
|
||||
|
||||
[HttpGet("job/list")]
|
||||
public async Task<IActionResult> GetJobTicketsList([FromQuery] Guid? projectId, [FromQuery] int pageNumber = 1, [FromQuery] int pageSize = 20, [FromQuery] bool isActive = true)
|
||||
{
|
||||
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.GetJobTicketsListAsync(projectId, pageNumber, pageSize, isActive, tenantId, loggedInEmploee);
|
||||
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpPost("job/create")]
|
||||
public async Task<IActionResult> CreateJobTicket(CreateJobTicketDto model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList();
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
|
||||
}
|
||||
Employee loggedInEmploee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _serviceProject.CreateJobTicketAsync(model, loggedInEmploee, tenantId);
|
||||
if (response.Success)
|
||||
|
||||
@ -16,6 +16,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Job Tickets Functions ===================================================================
|
||||
Task<ApiResponse<object>> GetJobTicketsListAsync(Guid? projectId, int pageNumber, int pageSize, bool isActive, Guid tenantId, Employee loggedInEmployee);
|
||||
Task<ApiResponse<object>> CreateJobTicketAsync(CreateJobTicketDto model, Employee loggedInEmployee, Guid tenantId);
|
||||
#endregion
|
||||
}
|
||||
|
||||
@ -362,6 +362,139 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
#region =================================================================== Job Tickets Functions ===================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a paginated, filtered list of job tickets for a tenant, including related project, status, assignees, and tags.
|
||||
/// </summary>
|
||||
/// <param name="projectId">Optional project filter.</param>
|
||||
/// <param name="pageNumber">Page index (1-based).</param>
|
||||
/// <param name="pageSize">Page size.</param>
|
||||
/// <param name="isActive">Active filter.</param>
|
||||
/// <param name="tenantId">Tenant context.</param>
|
||||
/// <param name="loggedInEmployee">Employee requesting data.</param>
|
||||
/// <returns>Paged list of JobTicketVM plus metadata, or error response.</returns>
|
||||
public async Task<ApiResponse<object>> GetJobTicketsListAsync(Guid? projectId, int pageNumber, int pageSize, bool isActive, Guid tenantId, Employee loggedInEmployee)
|
||||
{
|
||||
if (tenantId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("TenantId missing for job ticket fetch by employee {EmployeeId}", loggedInEmployee.Id);
|
||||
return ApiResponse<object>.ErrorResponse(
|
||||
"Access Denied",
|
||||
"Missing or invalid tenant context.",
|
||||
403);
|
||||
}
|
||||
|
||||
if (pageNumber < 1 || pageSize < 1)
|
||||
{
|
||||
_logger.LogInfo("Invalid paging parameters for job ticket fetch. PageNumber: {PageNumber}, PageSize: {PageSize}", pageNumber, pageSize);
|
||||
return ApiResponse<object>.ErrorResponse(
|
||||
"Bad Request",
|
||||
"Page number and size must be greater than zero.",
|
||||
400);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Build filtered query with necessary includes for eager loading
|
||||
var jobTicketQuery = _context.JobTickets
|
||||
.Include(jt => jt.Status)
|
||||
.Include(jt => jt.Project)
|
||||
.Include(jt => jt.CreatedBy).ThenInclude(e => e!.JobRole)
|
||||
.AsNoTracking()
|
||||
.Where(jt =>
|
||||
jt.TenantId == tenantId &&
|
||||
jt.IsActive == isActive &&
|
||||
jt.Project != null &&
|
||||
jt.Status != null &&
|
||||
jt.CreatedBy != null &&
|
||||
jt.CreatedBy.JobRole != null);
|
||||
|
||||
// Optionally filter by project
|
||||
if (projectId.HasValue)
|
||||
{
|
||||
var projectExists = await _context.ServiceProjects
|
||||
.AnyAsync(sp => sp.Id == projectId && sp.TenantId == tenantId && sp.IsActive);
|
||||
|
||||
if (!projectExists)
|
||||
{
|
||||
_logger.LogWarning("Requested service project not found. ProjectId: {ProjectId}, TenantId: {TenantId}", projectId, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Service project not found", "Service project not found for this tenant.", 404);
|
||||
}
|
||||
|
||||
jobTicketQuery = jobTicketQuery.Where(jt => jt.ProjectId == projectId.Value);
|
||||
}
|
||||
|
||||
// Total results and paging
|
||||
var totalEntities = await jobTicketQuery.CountAsync();
|
||||
var totalPages = (int)Math.Ceiling((double)totalEntities / pageSize);
|
||||
|
||||
// Fetch filtered/paged tickets
|
||||
var jobTickets = await jobTicketQuery
|
||||
.OrderByDescending(e => e.CreatedAt)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
var jobTicketIds = jobTickets.Select(jt => jt.Id).ToList();
|
||||
|
||||
// Fetch assignee and tag mappings concurrently using separate DbContexts for parallel IO
|
||||
var assigneeTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.JobEmployeeMappings
|
||||
.Include(jem => jem.Assignee).ThenInclude(e => e!.JobRole)
|
||||
.Where(jem => jobTicketIds.Contains(jem.JobTicketId) && jem.Assignee != null && jem.Assignee.JobRole != null && jem.TenantId == tenantId)
|
||||
.ToListAsync();
|
||||
});
|
||||
|
||||
var tagTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.JobTagMappings
|
||||
.Include(jtm => jtm.JobTag)
|
||||
.Where(jtm => jobTicketIds.Contains(jtm.JobTicketId) && jtm.JobTag != null && jtm.TenantId == tenantId)
|
||||
.ToListAsync();
|
||||
});
|
||||
|
||||
await Task.WhenAll(assigneeTask, tagTask);
|
||||
|
||||
var assigneeMappings = assigneeTask.Result;
|
||||
var tagMappings = tagTask.Result;
|
||||
|
||||
// Map tickets to view models and inject assignees/tags per ticket
|
||||
var jobTicketVMs = jobTickets.Select(jt =>
|
||||
{
|
||||
var vm = _mapper.Map<JobTicketVM>(jt);
|
||||
vm.Assignees = assigneeMappings
|
||||
.Where(jem => jem.JobTicketId == jt.Id)
|
||||
.Select(jem => _mapper.Map<BasicEmployeeVM>(jem.Assignee))
|
||||
.ToList();
|
||||
vm.Tags = tagMappings
|
||||
.Where(jtm => jtm.JobTicketId == jt.Id)
|
||||
.Select(jtm => _mapper.Map<TagVM>(jtm.JobTag))
|
||||
.ToList();
|
||||
return vm;
|
||||
}).ToList();
|
||||
|
||||
var response = new
|
||||
{
|
||||
CurrentPage = pageNumber,
|
||||
TotalPages = totalPages,
|
||||
TotalEntities = totalEntities,
|
||||
Data = jobTicketVMs,
|
||||
};
|
||||
|
||||
_logger.LogInfo("Job tickets fetched: {Count} tickets for tenant {TenantId}, page {PageNumber}", jobTicketVMs.Count, tenantId, pageNumber);
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(response, $"{jobTicketVMs.Count} job records fetched successfully.", 200);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception in fetching job tickets list for tenant {TenantId} by employee {EmployeeId}", tenantId, loggedInEmployee.Id);
|
||||
return ApiResponse<object>.ErrorResponse("Internal Server Error", "Unable to fetch job tickets list. Please try again later.", 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new job ticket with optional assignees and tags within a transactional scope.
|
||||
/// </summary>
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
"Title": "Dev"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMSOrg"
|
||||
"DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMS1"
|
||||
},
|
||||
"SmtpSettings": {
|
||||
"SmtpServer": "smtp.gmail.com",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user