Compare commits

..

No commits in common. "49b85c4df9e370d24143565a841c7174b6c1620d" and "67c8bee2c2624414ebf285d85786bc198501cbaa" have entirely different histories.

11 changed files with 40 additions and 4027 deletions

View File

@ -1,50 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Added_UploadedBy_ForeginKey_In_Decuments_Table : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "UploadedById",
table: "Documents",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci");
migrationBuilder.CreateIndex(
name: "IX_Documents_UploadedById",
table: "Documents",
column: "UploadedById");
migrationBuilder.AddForeignKey(
name: "FK_Documents_Employees_UploadedById",
table: "Documents",
column: "UploadedById",
principalTable: "Employees",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Documents_Employees_UploadedById",
table: "Documents");
migrationBuilder.DropIndex(
name: "IX_Documents_UploadedById",
table: "Documents");
migrationBuilder.DropColumn(
name: "UploadedById",
table: "Documents");
}
}
}

View File

@ -752,15 +752,10 @@ namespace Marco.Pms.DataAccess.Migrations
b.Property<DateTime>("UploadedAt") b.Property<DateTime>("UploadedAt")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.Property<Guid?>("UploadedById")
.HasColumnType("char(36)");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("TenantId"); b.HasIndex("TenantId");
b.HasIndex("UploadedById");
b.ToTable("Documents"); b.ToTable("Documents");
}); });
@ -2956,13 +2951,7 @@ namespace Marco.Pms.DataAccess.Migrations
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("Marco.Pms.Model.Employees.Employee", "UploadedBy")
.WithMany()
.HasForeignKey("UploadedById");
b.Navigation("Tenant"); b.Navigation("Tenant");
b.Navigation("UploadedBy");
}); });
modelBuilder.Entity("Marco.Pms.Model.Employees.Employee", b => modelBuilder.Entity("Marco.Pms.Model.Employees.Employee", b =>

View File

@ -1,7 +1,4 @@
using System.ComponentModel.DataAnnotations.Schema; using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Utilities;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace Marco.Pms.Model.DocumentManager namespace Marco.Pms.Model.DocumentManager
{ {
@ -19,15 +16,10 @@ namespace Marco.Pms.Model.DocumentManager
/// </summary> /// </summary>
public string? ThumbS3Key { get; set; } public string? ThumbS3Key { get; set; }
public string? Base64Data { get; set; } = null; public string? Base64Data { get; set; }
public long FileSize { get; set; } public long FileSize { get; set; }
public string ContentType { get; set; } = string.Empty; public string ContentType { get; set; } = string.Empty;
public Guid? UploadedById { get; set; }
[ValidateNever]
[ForeignKey("UploadedById")]
public Employee? UploadedBy { get; set; }
public DateTime UploadedAt { get; set; } public DateTime UploadedAt { get; set; }
} }
} }

View File

@ -1,10 +0,0 @@
using Marco.Pms.Model.DocumentManager;
namespace Marco.Pms.Model.Dtos.DocumentManager
{
public class DocumentBatchDto
{
public Guid? BatchId { get; set; }
public List<Document>? Documents { get; set; }
}
}

View File

@ -90,35 +90,29 @@ namespace Marco.Pms.Model.Mapper
}; };
} }
public static Document ToDocumentFromForumAttachmentDto(this ForumAttachmentDto AttachmentDto, string objectKey, string thumbS3Key, DateTime uploadedAt, public static Document ToDocumentFromForumAttachmentDto(this ForumAttachmentDto AttachmentDto, string objectKey, string thumbS3Key, DateTime uploadedAt, Guid tenantId)
Guid tenantId, Guid batchId, Guid loggedInEmployeeId)
{ {
return new Document return new Document
{ {
BatchId = batchId,
UploadedById = loggedInEmployeeId,
FileName = AttachmentDto.FileName, FileName = AttachmentDto.FileName,
ContentType = AttachmentDto.ContentType, ContentType = AttachmentDto.ContentType,
S3Key = objectKey, S3Key = objectKey,
ThumbS3Key = thumbS3Key, ThumbS3Key = thumbS3Key,
//Base64Data = AttachmentDto.Base64Data, Base64Data = AttachmentDto.Base64Data,
FileSize = AttachmentDto.FileSize, FileSize = AttachmentDto.FileSize,
UploadedAt = uploadedAt, UploadedAt = uploadedAt,
TenantId = tenantId TenantId = tenantId
}; };
} }
public static Document ToDocumentFromUpdateAttachmentDto(this UpdateAttachmentDto AttachmentDto, string objectKey, string thumbS3Key, DateTime uploadedAt, public static Document ToDocumentFromUpdateAttachmentDto(this UpdateAttachmentDto AttachmentDto, string objectKey, string thumbS3Key, DateTime uploadedAt, Guid tenantId)
Guid tenantId, Guid batchId, Guid loggedInEmployeeId)
{ {
return new Document return new Document
{ {
BatchId = batchId,
UploadedById = loggedInEmployeeId,
FileName = AttachmentDto.FileName, FileName = AttachmentDto.FileName,
ContentType = AttachmentDto.ContentType, ContentType = AttachmentDto.ContentType,
S3Key = objectKey, S3Key = objectKey,
ThumbS3Key = thumbS3Key, ThumbS3Key = thumbS3Key,
//Base64Data = AttachmentDto.Base64Data, Base64Data = AttachmentDto.Base64Data,
FileSize = AttachmentDto.FileSize, FileSize = AttachmentDto.FileSize,
UploadedAt = uploadedAt, UploadedAt = uploadedAt,
TenantId = tenantId TenantId = tenantId

View File

@ -1,14 +0,0 @@
namespace Marco.Pms.Model.Utilities
{
public class ImageFilter
{
public List<Guid>? BuildingIds { get; set; }
public List<Guid>? FloorIds { get; set; }
public List<Guid>? WorkAreaIds { get; set; }
public List<Guid>? WorkCategoryIds { get; set; }
public List<Guid>? ActivityIds { get; set; }
public List<Guid>? UploadedByIds { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
}
}

View File

@ -603,8 +603,7 @@ namespace MarcoBMS.Services.Controllers
} }
Guid tenantId = GetTenantId(); Guid tenantId = GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var currentEmployee = await _userHelper.GetCurrentEmployeeAsync();
var batchId = Guid.NewGuid();
using var transaction = await _context.Database.BeginTransactionAsync(); using var transaction = await _context.Database.BeginTransactionAsync();
try try
@ -705,12 +704,10 @@ namespace MarcoBMS.Services.Controllers
document = new Document document = new Document
{ {
BatchId = batchId,
UploadedById = loggedInEmployee.Id,
FileName = recordAttendanceDot.Image.FileName ?? "", FileName = recordAttendanceDot.Image.FileName ?? "",
ContentType = recordAttendanceDot.Image.ContentType, ContentType = recordAttendanceDot.Image.ContentType,
S3Key = objectKey, S3Key = objectKey,
//Base64Data = recordAttendanceDot.Image.Base64Data, Base64Data = recordAttendanceDot.Image.Base64Data,
FileSize = recordAttendanceDot.Image.FileSize, FileSize = recordAttendanceDot.Image.FileSize,
UploadedAt = recordAttendanceDot.Date, UploadedAt = recordAttendanceDot.Date,
TenantId = tenantId TenantId = tenantId
@ -731,7 +728,7 @@ namespace MarcoBMS.Services.Controllers
Longitude = recordAttendanceDot.Longitude, Longitude = recordAttendanceDot.Longitude,
DocumentId = document?.Id, DocumentId = document?.Id,
TenantId = tenantId, TenantId = tenantId,
UpdatedBy = loggedInEmployee.Id, UpdatedBy = recordAttendanceDot.EmployeeID,
UpdatedOn = recordAttendanceDot.Date UpdatedOn = recordAttendanceDot.Date
}; };
_context.AttendanceLogs.Add(attendanceLog); _context.AttendanceLogs.Add(attendanceLog);
@ -758,7 +755,7 @@ namespace MarcoBMS.Services.Controllers
var notification = new var notification = new
{ {
LoggedInUserId = loggedInEmployee.Id, LoggedInUserId = currentEmployee.Id,
Keyword = "Attendance", Keyword = "Attendance",
Activity = recordAttendanceDot.Id == Guid.Empty ? 1 : 0, Activity = recordAttendanceDot.Id == Guid.Empty ? 1 : 0,
ProjectId = attendance.ProjectID, ProjectId = attendance.ProjectID,

View File

@ -48,8 +48,6 @@ namespace Marco.Pms.Services.Controllers
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400)); return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
} }
Guid tenantId = _userHelper.GetTenantId(); Guid tenantId = _userHelper.GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var batchId = Guid.NewGuid();
TicketForum ticketForum = createTicketDto.ToTicketForumFromCreateTicketDto(tenantId); TicketForum ticketForum = createTicketDto.ToTicketForumFromCreateTicketDto(tenantId);
_context.Tickets.Add(ticketForum); _context.Tickets.Add(ticketForum);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
@ -81,7 +79,7 @@ namespace Marco.Pms.Services.Controllers
string objectKey = $"tenant-{tenantId}/project-{createTicketDto.LinkedProjectId}/froum/{fileName}"; string objectKey = $"tenant-{tenantId}/project-{createTicketDto.LinkedProjectId}/froum/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey); await _s3Service.UploadFileAsync(base64, fileType, objectKey);
Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, createTicketDto.CreatedAt, tenantId, batchId, loggedInEmployee.Id); Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, createTicketDto.CreatedAt, tenantId);
_context.Documents.Add(document); _context.Documents.Add(document);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
@ -164,15 +162,7 @@ namespace Marco.Pms.Services.Controllers
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400)); return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", errors, 400));
} }
Guid tenantId = _userHelper.GetTenantId(); Guid tenantId = _userHelper.GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var existingTicket = await _context.Tickets.Include(t => t.TicketTypeMaster).Include(t => t.TicketStatusMaster).Include(t => t.Priority).AsNoTracking().FirstOrDefaultAsync(t => t.Id == updateTicketDto.Id);
var batchId = Guid.NewGuid();
var existingTicket = await _context.Tickets
.Include(t => t.TicketTypeMaster)
.Include(t => t.TicketStatusMaster)
.Include(t => t.Priority)
.AsNoTracking()
.FirstOrDefaultAsync(t => t.Id == updateTicketDto.Id);
if (existingTicket != null) if (existingTicket != null)
{ {
TicketForum ticketForum = updateTicketDto.ToTicketForumFromUpdateTicketDto(existingTicket); TicketForum ticketForum = updateTicketDto.ToTicketForumFromUpdateTicketDto(existingTicket);
@ -212,7 +202,7 @@ namespace Marco.Pms.Services.Controllers
string objectKey = $"tenant-{tenantId}/project-{updateTicketDto.LinkedProjectId}/froum/{fileName}"; string objectKey = $"tenant-{tenantId}/project-{updateTicketDto.LinkedProjectId}/froum/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey); await _s3Service.UploadFileAsync(base64, fileType, objectKey);
Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, updateTicketDto.CreatedAt, tenantId, batchId, loggedInEmployee.Id); Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, updateTicketDto.CreatedAt, tenantId);
_context.Documents.Add(document); _context.Documents.Add(document);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
@ -354,9 +344,6 @@ namespace Marco.Pms.Services.Controllers
} }
Guid tenantId = _userHelper.GetTenantId(); Guid tenantId = _userHelper.GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var batchId = Guid.NewGuid();
List<TicketAttachment> attachments = new List<TicketAttachment>(); List<TicketAttachment> attachments = new List<TicketAttachment>();
List<Document> documents = new List<Document>(); List<Document> documents = new List<Document>();
@ -394,7 +381,7 @@ namespace Marco.Pms.Services.Controllers
string objectKey = $"tenant-{tenantId}/project-{ticket.LinkedProjectId}/froum/{fileName}"; string objectKey = $"tenant-{tenantId}/project-{ticket.LinkedProjectId}/froum/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey); await _s3Service.UploadFileAsync(base64, fileType, objectKey);
Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, addCommentDto.SentAt, tenantId, batchId, loggedInEmployee.Id); Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, addCommentDto.SentAt, tenantId);
_context.Documents.Add(document); _context.Documents.Add(document);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
@ -442,9 +429,6 @@ namespace Marco.Pms.Services.Controllers
} }
Guid tenantId = _userHelper.GetTenantId(); Guid tenantId = _userHelper.GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var batchId = Guid.NewGuid();
List<TicketAttachment> attachments = new List<TicketAttachment>(); List<TicketAttachment> attachments = new List<TicketAttachment>();
TicketForum? ticket = await _context.Tickets.FirstOrDefaultAsync(t => t.Id == updateCommentDto.TicketId); TicketForum? ticket = await _context.Tickets.FirstOrDefaultAsync(t => t.Id == updateCommentDto.TicketId);
@ -489,7 +473,7 @@ namespace Marco.Pms.Services.Controllers
string objectKey = $"tenant-{tenantId}/project-{ticket.LinkedProjectId}/froum/{fileName}"; string objectKey = $"tenant-{tenantId}/project-{ticket.LinkedProjectId}/froum/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey); await _s3Service.UploadFileAsync(base64, fileType, objectKey);
Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, existingComment.SentAt, tenantId, batchId, loggedInEmployee.Id); Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, existingComment.SentAt, tenantId);
_context.Documents.Add(document); _context.Documents.Add(document);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
@ -557,9 +541,6 @@ namespace Marco.Pms.Services.Controllers
} }
Guid tenantId = _userHelper.GetTenantId(); Guid tenantId = _userHelper.GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var batchId = Guid.NewGuid();
List<TicketAttachmentVM> ticketAttachmentVMs = new List<TicketAttachmentVM>(); List<TicketAttachmentVM> ticketAttachmentVMs = new List<TicketAttachmentVM>();
List<Guid> ticketIds = forumAttachmentDtos.Select(f => f.TicketId.HasValue ? f.TicketId.Value : Guid.Empty).ToList(); List<Guid> ticketIds = forumAttachmentDtos.Select(f => f.TicketId.HasValue ? f.TicketId.Value : Guid.Empty).ToList();
@ -598,7 +579,7 @@ namespace Marco.Pms.Services.Controllers
string objectKey = $"tenant-{tenantId}/project-{ticket?.LinkedProjectId}/froum/{fileName}"; string objectKey = $"tenant-{tenantId}/project-{ticket?.LinkedProjectId}/froum/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey); await _s3Service.UploadFileAsync(base64, fileType, objectKey);
Document document = forumAttachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, forumAttachmentDto.SentAt, tenantId, batchId, loggedInEmployee.Id); Document document = forumAttachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, forumAttachmentDto.SentAt, tenantId);
_context.Documents.Add(document); _context.Documents.Add(document);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();

View File

@ -1,404 +0,0 @@
using System.Text.Json;
using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Activities;
using Marco.Pms.Model.Dtos.DocumentManager;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Services.Service;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
namespace Marco.Pms.Services.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ImageController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly S3UploadService _s3Service;
private readonly UserHelper _userHelper;
private readonly ILoggingService _logger;
private readonly PermissionServices _permission;
private readonly Guid tenantId;
public ImageController(ApplicationDbContext context, S3UploadService s3Service, UserHelper userHelper, ILoggingService logger, PermissionServices permission)
{
_context = context;
_s3Service = s3Service;
_userHelper = userHelper;
_logger = logger;
tenantId = userHelper.GetTenantId();
_permission = permission;
}
[HttpGet("images/{projectId}")]
public async Task<IActionResult> GetImageList(Guid projectId, [FromQuery] string? filter, [FromQuery] int? pageNumber = 1, [FromQuery] int? pageSize = 10)
{
_logger.LogInfo("[GetImageList] Called by Employee for ProjectId: {ProjectId}", projectId);
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Step 1: Validate project existence
var isProjectExist = await _context.Projects.AnyAsync(p => p.Id == projectId && p.TenantId == tenantId);
if (!isProjectExist)
{
_logger.LogWarning("[GetImageList] ProjectId: {ProjectId} not found", projectId);
return BadRequest(ApiResponse<object>.ErrorResponse("Project not found", "Project not found in database", 400));
}
// Step 2: Check project access permission
var hasPermission = await _permission.HasProjectPermission(loggedInEmployee, projectId.ToString());
if (!hasPermission)
{
_logger.LogWarning("[GetImageList] Access denied for EmployeeId: {EmployeeId} on ProjectId: {ProjectId}", loggedInEmployee.Id, projectId);
return StatusCode(403, ApiResponse<object>.ErrorResponse("You don't have access", "You don't have access", 403));
}
// Step 3: Deserialize filter
ImageFilter? imageFilter = null;
if (!string.IsNullOrWhiteSpace(filter))
{
try
{
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
//string unescapedJsonString = JsonSerializer.Deserialize<string>(filter, options) ?? "";
//imageFilter = JsonSerializer.Deserialize<ImageFilter>(unescapedJsonString, options);
imageFilter = JsonSerializer.Deserialize<ImageFilter>(filter, options);
}
catch (Exception ex)
{
_logger.LogWarning("[GetImageList] Failed to parse filter: {Message}", ex.Message);
}
}
// Step 4: Extract filter values
var buildingIds = imageFilter?.BuildingIds;
var floorIds = imageFilter?.FloorIds;
var workAreaIds = imageFilter?.WorkAreaIds;
var activityIds = imageFilter?.ActivityIds;
var workCategoryIds = imageFilter?.WorkCategoryIds;
var startDate = imageFilter?.StartDate;
var endDate = imageFilter?.EndDate;
var uploadedByIds = imageFilter?.UploadedByIds;
// Step 5: Fetch building > floor > area > work item hierarchy
List<Building>? buildings = null;
List<Floor>? floors = null;
List<WorkArea>? workAreas = null;
if (buildingIds != null && buildingIds.Count > 0)
{
buildings = await _context.Buildings
.Where(b => b.ProjectId == projectId && buildingIds.Contains(b.Id))
.ToListAsync();
}
else
{
buildings = await _context.Buildings
.Where(b => b.ProjectId == projectId)
.ToListAsync();
buildingIds = buildings.Select(b => b.Id).ToList();
}
if (floorIds != null && floorIds.Count > 0)
{
floors = await _context.Floor
.Where(f => buildingIds.Contains(f.BuildingId) && floorIds.Contains(f.Id))
.ToListAsync();
}
else
{
floors = await _context.Floor
.Where(f => buildingIds.Contains(f.BuildingId))
.ToListAsync();
floorIds = floors.Select(f => f.Id).ToList();
}
if (workAreaIds != null && workAreaIds.Count > 0)
{
workAreas = await _context.WorkAreas
.Where(wa => floorIds.Contains(wa.FloorId) && workAreaIds.Contains(wa.Id))
.ToListAsync();
}
else
{
workAreas = await _context.WorkAreas
.Where(wa => floorIds.Contains(wa.FloorId))
.ToListAsync();
workAreaIds = workAreas.Select(wa => wa.Id).ToList();
}
var workItemsQuery = _context.WorkItems.Include(w => w.ActivityMaster).Include(w => w.WorkCategoryMaster)
.Where(wi => workAreaIds.Contains(wi.WorkAreaId));
if (activityIds?.Any() == true) workItemsQuery = workItemsQuery.Where(wi => activityIds.Contains(wi.ActivityId));
if (workCategoryIds?.Any() == true)
{
workItemsQuery = workItemsQuery.Where(wi => wi.WorkCategoryMaster != null && workCategoryIds.Contains(wi.WorkCategoryMaster.Id));
}
var workItems = await workItemsQuery.ToListAsync();
var workItemIds = workItems.Select(wi => wi.Id).ToList();
// Step 6: Fetch task allocations and comments
var tasks = await _context.TaskAllocations.Include(t => t.ReportedBy)
.Where(t => workItemIds.Contains(t.WorkItemId)).ToListAsync();
var taskIds = tasks.Select(t => t.Id).ToList();
var comments = await _context.TaskComments.Include(c => c.Employee)
.Where(c => taskIds.Contains(c.TaskAllocationId)).ToListAsync();
var commentIds = comments.Select(c => c.Id).ToList();
var attachments = await _context.TaskAttachments
.Where(ta => taskIds.Contains(ta.ReferenceId) || commentIds.Contains(ta.ReferenceId)).ToListAsync();
var documentIds = attachments.Select(ta => ta.DocumentId).ToList();
// Step 7: Fetch and filter documents
List<DocumentBatchDto> documents = new List<DocumentBatchDto>();
var docQuery = _context.Documents.Include(d => d.UploadedBy)
.Where(d => documentIds.Contains(d.Id) && d.TenantId == tenantId);
if (startDate != null && endDate != null)
{
docQuery = docQuery.Where(d => d.UploadedAt.Date >= startDate.Value.Date && d.UploadedAt.Date <= endDate.Value.Date);
}
if (pageNumber != null && pageSize != null)
{
documents = await docQuery
.GroupBy(d => d.BatchId)
.OrderByDescending(g => g.Max(d => d.UploadedAt))
.Skip((pageNumber.Value - 1) * pageSize.Value)
.Take(pageSize.Value)
.Select(g => new DocumentBatchDto
{
BatchId = g.Key,
Documents = g.ToList()
})
.ToListAsync();
Console.Write("Pagenation Success");
}
// Step 8: Build response
var documentVM = documents.Select(d =>
{
var docIds = d.Documents?.Select(x => x.Id).ToList() ?? new List<Guid>();
var refId = attachments.FirstOrDefault(ta => docIds.Contains(ta.DocumentId))?.ReferenceId;
var task = tasks.FirstOrDefault(t => t.Id == refId);
var comment = comments.FirstOrDefault(c => c.Id == refId);
var source = task != null ? "Report" : comment != null ? "Comment" : "";
var uploadedBy = task?.ReportedBy ?? comment?.Employee;
if (comment != null)
{
task = tasks.FirstOrDefault(t => t.Id == comment.TaskAllocationId);
}
if (task != null)
{
comment = comments.OrderBy(c => c.CommentDate).FirstOrDefault(c => c.TaskAllocationId == task.Id);
}
var workItem = workItems.FirstOrDefault(w => w.Id == task?.WorkItemId);
var workArea = workAreas.FirstOrDefault(wa => wa.Id == workItem?.WorkAreaId);
var floor = floors.FirstOrDefault(f => f.Id == workArea?.FloorId);
var building = buildings.FirstOrDefault(b => b.Id == floor?.BuildingId);
return new
{
BatchId = d.BatchId,
Documents = d.Documents?.Select(x => new
{
Id = x.Id,
thumbnailUrl = x.ThumbS3Key != null ? _s3Service.GeneratePreSignedUrlAsync(x.ThumbS3Key) : (x.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(x.S3Key) : null),
Url = x.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(x.S3Key) : null,
UploadedBy = x.UploadedBy?.ToBasicEmployeeVMFromEmployee() ?? uploadedBy?.ToBasicEmployeeVMFromEmployee(),
UploadedAt = x.UploadedAt,
}).ToList(),
Source = source,
ProjectId = projectId,
BuildingId = building?.Id,
BuildingName = building?.Name,
FloorIds = floor?.Id,
FloorName = floor?.FloorName,
WorkAreaId = workArea?.Id,
WorkAreaName = workArea?.AreaName,
TaskId = task?.Id,
ActivityId = workItem?.ActivityMaster?.Id,
ActivityName = workItem?.ActivityMaster?.ActivityName,
WorkCategoryId = workItem?.WorkCategoryMaster?.Id,
WorkCategoryName = workItem?.WorkCategoryMaster?.Name,
CommentId = comment?.Id,
Comment = comment?.Comment
};
}).ToList();
if (uploadedByIds?.Any() == true)
{
documentVM = documentVM.Where(d => d.Documents != null && d.Documents.Any(x => uploadedByIds.Contains(x.UploadedBy?.Id ?? Guid.Empty))).ToList();
}
_logger.LogInfo("[GetImageList] Fetched {Count} documents for ProjectId: {ProjectId}", documentVM.Count, projectId);
return Ok(ApiResponse<object>.SuccessResponse(documentVM, $"{documentVM.Count} image records fetched successfully", 200));
}
[HttpGet("batch/{batchId}")]
public async Task<IActionResult> GetImagesByBatch(Guid batchId)
{
_logger.LogInfo("GetImagesByBatch called for BatchId: {BatchId}", batchId);
// Step 1: Get the logged-in employee
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Step 2: Retrieve all documents in the batch
var documents = await _context.Documents
.Include(d => d.UploadedBy)
.Where(d => d.BatchId == batchId)
.ToListAsync();
if (!documents.Any())
{
_logger.LogWarning("No documents found for BatchId: {BatchId}", batchId);
return NotFound(ApiResponse<object>.ErrorResponse("No images found", "No images associated with this batch", 404));
}
var documentIds = documents.Select(d => d.Id).ToList();
// Step 3: Get task/comment reference IDs linked to these documents
var referenceIds = await _context.TaskAttachments
.Where(ta => documentIds.Contains(ta.DocumentId))
.Select(ta => ta.ReferenceId)
.Distinct()
.ToListAsync();
// Step 4: Try to identify the source of the attachment (task or comment)
var task = await _context.TaskAllocations
.Include(t => t.ReportedBy)
.FirstOrDefaultAsync(t => referenceIds.Contains(t.Id));
TaskComment? comment = null;
WorkItem? workItem = null;
Employee? uploadedBy = null;
string source = "";
if (task != null)
{
uploadedBy = task.ReportedBy;
workItem = await _context.WorkItems
.Include(wi => wi.ActivityMaster)
.Include(wi => wi.WorkCategoryMaster)
.FirstOrDefaultAsync(wi => wi.Id == task.WorkItemId);
source = "Report";
}
else
{
comment = await _context.TaskComments
.Include(tc => tc.TaskAllocation)
.Include(tc => tc.Employee)
.FirstOrDefaultAsync(tc => referenceIds.Contains(tc.Id));
var workItemId = comment?.TaskAllocation?.WorkItemId;
uploadedBy = comment?.Employee;
workItem = await _context.WorkItems
.Include(wi => wi.ActivityMaster)
.Include(wi => wi.WorkCategoryMaster)
.FirstOrDefaultAsync(wi => wi.Id == workItemId);
source = "Comment";
}
// Step 5: Traverse up to building level
var workAreaId = workItem?.WorkAreaId;
var workArea = await _context.WorkAreas
.Include(wa => wa.Floor)
.FirstOrDefaultAsync(wa => wa.Id == workAreaId);
var buildingId = workArea?.Floor?.BuildingId;
var building = await _context.Buildings
.FirstOrDefaultAsync(b => b.Id == buildingId);
// Step 6: Construct the response
var response = new
{
BatchId = batchId,
Documents = documents?.Select(x => new
{
Id = x.Id,
thumbnailUrl = x.ThumbS3Key != null ? _s3Service.GeneratePreSignedUrlAsync(x.ThumbS3Key) : (x.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(x.S3Key) : null),
Url = x.S3Key != null ? _s3Service.GeneratePreSignedUrlAsync(x.S3Key) : null,
UploadedBy = x.UploadedBy?.ToBasicEmployeeVMFromEmployee() ?? uploadedBy?.ToBasicEmployeeVMFromEmployee(),
UploadedAt = x.UploadedAt,
}).ToList(),
Source = source,
ProjectId = building?.ProjectId,
BuildingId = building?.Id,
BuildingName = building?.Name,
FloorIds = workArea?.Floor?.Id,
FloorName = workArea?.Floor?.FloorName,
WorkAreaId = workArea?.Id,
WorkAreaName = workArea?.AreaName,
TaskId = task?.Id,
ActivityId = workItem?.ActivityMaster?.Id,
ActivityName = workItem?.ActivityMaster?.ActivityName,
WorkCategoryId = workItem?.WorkCategoryMaster?.Id,
WorkCategoryName = workItem?.WorkCategoryMaster?.Name,
CommentId = comment?.Id,
Comment = comment?.Comment
};
_logger.LogInfo("Fetched {Count} image(s) for BatchId: {BatchId}", response.Documents?.Count ?? 0, batchId);
return Ok(ApiResponse<object>.SuccessResponse(response, "Images for provided batchId fetched successfully", 200));
}
[HttpGet("{documentId}")]
public async Task<IActionResult> GetImage(Guid documentId)
{
// Log the start of the image fetch process
_logger.LogInfo("GetImage called for DocumentId: {DocumentId}", documentId);
// Step 1: Get the currently logged-in employee (for future use like permission checks or auditing)
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
// Step 2: Fetch the document from the database based on the provided ID
var document = await _context.Documents.FirstOrDefaultAsync(d => d.Id == documentId);
// Step 3: If document doesn't exist, return a 400 Bad Request response
if (document == null)
{
_logger.LogWarning("Document not found for DocumentId: {DocumentId}", documentId);
return BadRequest(ApiResponse<object>.ErrorResponse("Document not found", "Document not found", 400));
}
// Step 4: Generate pre-signed URLs for thumbnail and full image (if keys exist)
string? thumbnailUrl = document.ThumbS3Key != null
? _s3Service.GeneratePreSignedUrlAsync(document.ThumbS3Key)
: null;
string? imageUrl = document.S3Key != null
? _s3Service.GeneratePreSignedUrlAsync(document.S3Key)
: null;
// Step 5: Prepare the response object
var response = new
{
ThumbnailUrl = thumbnailUrl,
ImageUrl = imageUrl
};
// Step 6: Log successful fetch and return the result
_logger.LogInfo("Image fetched successfully for DocumentId: {DocumentId}", documentId);
return Ok(ApiResponse<object>.SuccessResponse(response, "Image fetched successfully", 200));
}
}
}

View File

@ -7,13 +7,11 @@ using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities; using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Activities; using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Services.Helpers; using Marco.Pms.Services.Helpers;
using Marco.Pms.Services.Hubs;
using Marco.Pms.Services.Service; using Marco.Pms.Services.Service;
using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service; using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Document = Marco.Pms.Model.DocumentManager.Document; using Document = Marco.Pms.Model.DocumentManager.Document;
@ -30,20 +28,17 @@ namespace MarcoBMS.Services.Controllers
private readonly UserHelper _userHelper; private readonly UserHelper _userHelper;
private readonly S3UploadService _s3Service; private readonly S3UploadService _s3Service;
private readonly ILoggingService _logger; private readonly ILoggingService _logger;
private readonly IHubContext<MarcoHub> _signalR;
private readonly PermissionServices _permissionServices; private readonly PermissionServices _permissionServices;
private readonly CacheUpdateHelper _cache; private readonly CacheUpdateHelper _cache;
private readonly Guid Approve_Task; private readonly Guid Approve_Task;
private readonly Guid Assign_Report_Task; private readonly Guid Assign_Report_Task;
public TaskController(ApplicationDbContext context, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permissionServices, public TaskController(ApplicationDbContext context, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permissionServices, CacheUpdateHelper cache)
IHubContext<MarcoHub> signalR, CacheUpdateHelper cache)
{ {
_context = context; _context = context;
_userHelper = userHelper; _userHelper = userHelper;
_s3Service = s3Service; _s3Service = s3Service;
_logger = logger; _logger = logger;
_signalR = signalR;
_permissionServices = permissionServices; _permissionServices = permissionServices;
_cache = cache; _cache = cache;
Approve_Task = Guid.Parse("db4e40c5-2ba9-4b6d-b8a6-a16a250ff99c"); Approve_Task = Guid.Parse("db4e40c5-2ba9-4b6d-b8a6-a16a250ff99c");
@ -204,22 +199,16 @@ namespace MarcoBMS.Services.Controllers
var comment = reportTask.ToCommentFromReportTaskDto(tenantId, loggedInEmployee.Id); var comment = reportTask.ToCommentFromReportTaskDto(tenantId, loggedInEmployee.Id);
_context.TaskComments.Add(comment); _context.TaskComments.Add(comment);
int numberofImages = 0;
var workAreaId = taskAllocation.WorkItem?.WorkAreaId;
var workArea = await _context.WorkAreas.Include(a => a.Floor)
.FirstOrDefaultAsync(a => a.Id == workAreaId) ?? new WorkArea();
var buildingId = workArea.Floor?.BuildingId;
var building = await _context.Buildings
.FirstOrDefaultAsync(b => b.Id == buildingId);
var batchId = Guid.NewGuid();
var projectId = building?.ProjectId;
if (reportTask.Images?.Any() == true) if (reportTask.Images?.Any() == true)
{ {
var workAreaId = taskAllocation.WorkItem?.WorkAreaId;
var workArea = await _context.WorkAreas.Include(a => a.Floor)
.FirstOrDefaultAsync(a => a.Id == workAreaId) ?? new WorkArea();
var buildingId = workArea.Floor?.BuildingId;
var building = await _context.Buildings
.FirstOrDefaultAsync(b => b.Id == buildingId);
foreach (var image in reportTask.Images) foreach (var image in reportTask.Images)
{ {
@ -235,18 +224,16 @@ namespace MarcoBMS.Services.Controllers
var fileType = _s3Service.GetContentTypeFromBase64(base64); var fileType = _s3Service.GetContentTypeFromBase64(base64);
var fileName = _s3Service.GenerateFileName(fileType, tenantId, "task_report"); var fileName = _s3Service.GenerateFileName(fileType, tenantId, "task_report");
var objectKey = $"tenant-{tenantId}/project-{projectId}/Actitvity/{fileName}"; var objectKey = $"tenant-{tenantId}/project-{building?.ProjectId}/Actitvity/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey); await _s3Service.UploadFileAsync(base64, fileType, objectKey);
var document = new Document var document = new Document
{ {
BatchId = batchId,
UploadedById = loggedInEmployee.Id,
FileName = image.FileName ?? "", FileName = image.FileName ?? "",
ContentType = image.ContentType ?? "", ContentType = image.ContentType ?? "",
S3Key = objectKey, S3Key = objectKey,
//Base64Data = image.Base64Data, Base64Data = image.Base64Data,
FileSize = image.FileSize, FileSize = image.FileSize,
UploadedAt = DateTime.UtcNow, UploadedAt = DateTime.UtcNow,
TenantId = tenantId TenantId = tenantId
@ -259,7 +246,6 @@ namespace MarcoBMS.Services.Controllers
ReferenceId = reportTask.Id ReferenceId = reportTask.Id
}; };
_context.TaskAttachments.Add(attachment); _context.TaskAttachments.Add(attachment);
numberofImages += 1;
} }
} }
@ -277,9 +263,6 @@ namespace MarcoBMS.Services.Controllers
response.Comments = comments.Select(c => c.ToCommentVMFromTaskComment()).ToList(); response.Comments = comments.Select(c => c.ToCommentVMFromTaskComment()).ToList();
response.checkList = checkListVMs; response.checkList = checkListVMs;
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Task_Report", NumberOfImages = numberofImages, ProjectId = projectId };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
_logger.LogInfo("Task {TaskId} reported successfully by Employee {EmployeeId}", taskAllocation.Id, loggedInEmployee.Id); _logger.LogInfo("Task {TaskId} reported successfully by Employee {EmployeeId}", taskAllocation.Id, loggedInEmployee.Id);
return Ok(ApiResponse<object>.SuccessResponse(response, "Task reported successfully", 200)); return Ok(ApiResponse<object>.SuccessResponse(response, "Task reported successfully", 200));
@ -291,7 +274,7 @@ namespace MarcoBMS.Services.Controllers
_logger.LogInfo("AddCommentForTask called for TaskAllocationId: {TaskId}", createComment.TaskAllocationId); _logger.LogInfo("AddCommentForTask called for TaskAllocationId: {TaskId}", createComment.TaskAllocationId);
var tenantId = GetTenantId(); var tenantId = GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); var employee = await _userHelper.GetCurrentEmployeeAsync();
// Validate Task Allocation and associated WorkItem // Validate Task Allocation and associated WorkItem
var taskAllocation = await _context.TaskAllocations var taskAllocation = await _context.TaskAllocations
@ -311,18 +294,15 @@ namespace MarcoBMS.Services.Controllers
var buildingId = workArea.Floor?.BuildingId ?? Guid.Empty; var buildingId = workArea.Floor?.BuildingId ?? Guid.Empty;
var building = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == buildingId); var building = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == buildingId);
var projectId = building?.ProjectId;
// Save comment // Save comment
var comment = createComment.ToCommentFromCommentDto(tenantId, loggedInEmployee.Id); var comment = createComment.ToCommentFromCommentDto(tenantId, employee.Id);
_context.TaskComments.Add(comment); _context.TaskComments.Add(comment);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
_logger.LogInfo("Comment saved with Id: {CommentId}", comment.Id); _logger.LogInfo("Comment saved with Id: {CommentId}", comment.Id);
// Process image uploads // Process image uploads
var images = createComment.Images; var images = createComment.Images;
var batchId = Guid.NewGuid();
int numberofImages = 0;
if (images != null && images.Any()) if (images != null && images.Any())
{ {
@ -341,19 +321,17 @@ namespace MarcoBMS.Services.Controllers
var fileType = _s3Service.GetContentTypeFromBase64(base64); var fileType = _s3Service.GetContentTypeFromBase64(base64);
var fileName = _s3Service.GenerateFileName(fileType, tenantId, "task_comment"); var fileName = _s3Service.GenerateFileName(fileType, tenantId, "task_comment");
var objectKey = $"tenant-{tenantId}/project-{projectId}/Activity/{fileName}"; var objectKey = $"tenant-{tenantId}/project-{building?.ProjectId}/Activity/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey); await _s3Service.UploadFileAsync(base64, fileType, objectKey);
_logger.LogInfo("Image uploaded to S3 with key: {ObjectKey}", objectKey); _logger.LogInfo("Image uploaded to S3 with key: {ObjectKey}", objectKey);
var document = new Document var document = new Document
{ {
BatchId = batchId,
UploadedById = loggedInEmployee.Id,
FileName = image.FileName ?? string.Empty, FileName = image.FileName ?? string.Empty,
ContentType = image.ContentType ?? fileType, ContentType = image.ContentType ?? fileType,
S3Key = objectKey, S3Key = objectKey,
//Base64Data = image.Base64Data, Base64Data = image.Base64Data,
FileSize = image.FileSize, FileSize = image.FileSize,
UploadedAt = DateTime.UtcNow, UploadedAt = DateTime.UtcNow,
TenantId = tenantId TenantId = tenantId
@ -368,7 +346,6 @@ namespace MarcoBMS.Services.Controllers
}; };
_context.TaskAttachments.Add(attachment); _context.TaskAttachments.Add(attachment);
numberofImages += 1;
} }
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
@ -379,9 +356,6 @@ namespace MarcoBMS.Services.Controllers
var response = comment.ToCommentVMFromTaskComment(); var response = comment.ToCommentVMFromTaskComment();
_logger.LogInfo("Returning response for commentId: {CommentId}", comment.Id); _logger.LogInfo("Returning response for commentId: {CommentId}", comment.Id);
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Task_Comment", NumberOfImages = numberofImages, ProjectId = projectId };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
return Ok(ApiResponse<object>.SuccessResponse(response, "Comment saved successfully", 200)); return Ok(ApiResponse<object>.SuccessResponse(response, "Comment saved successfully", 200));
} }
@ -755,21 +729,17 @@ namespace MarcoBMS.Services.Controllers
}; };
_context.TaskComments.Add(comment); _context.TaskComments.Add(comment);
var workAreaId = taskAllocation.WorkItem?.WorkAreaId;
var workArea = await _context.WorkAreas.Include(a => a.Floor)
.FirstOrDefaultAsync(a => a.Id == workAreaId) ?? new WorkArea();
var buildingId = workArea.Floor?.BuildingId;
var building = await _context.Buildings
.FirstOrDefaultAsync(b => b.Id == buildingId);
var projectId = building?.ProjectId;
int numberofImages = 0;
// Handle image attachments, if any // Handle image attachments, if any
if (approveTask.Images?.Count > 0) if (approveTask.Images?.Count > 0)
{ {
var batchId = Guid.NewGuid(); var workAreaId = taskAllocation.WorkItem?.WorkAreaId;
var workArea = await _context.WorkAreas.Include(a => a.Floor)
.FirstOrDefaultAsync(a => a.Id == workAreaId) ?? new WorkArea();
var buildingId = workArea.Floor?.BuildingId;
var building = await _context.Buildings
.FirstOrDefaultAsync(b => b.Id == buildingId);
foreach (var image in approveTask.Images) foreach (var image in approveTask.Images)
{ {
@ -783,18 +753,16 @@ namespace MarcoBMS.Services.Controllers
var fileType = _s3Service.GetContentTypeFromBase64(base64); var fileType = _s3Service.GetContentTypeFromBase64(base64);
var fileName = _s3Service.GenerateFileName(fileType, tenantId, "task_comment"); var fileName = _s3Service.GenerateFileName(fileType, tenantId, "task_comment");
var objectKey = $"tenant-{tenantId}/project-{projectId}/Activity/{fileName}"; var objectKey = $"tenant-{tenantId}/project-{building?.ProjectId}/Activity/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey); await _s3Service.UploadFileAsync(base64, fileType, objectKey);
var document = new Document var document = new Document
{ {
BatchId = batchId,
UploadedById = loggedInEmployee.Id,
FileName = fileName, FileName = fileName,
ContentType = image.ContentType ?? string.Empty, ContentType = image.ContentType ?? string.Empty,
S3Key = objectKey, S3Key = objectKey,
//Base64Data = image.Base64Data, Base64Data = image.Base64Data,
FileSize = image.FileSize, FileSize = image.FileSize,
UploadedAt = DateTime.UtcNow, UploadedAt = DateTime.UtcNow,
TenantId = tenantId TenantId = tenantId
@ -811,16 +779,12 @@ namespace MarcoBMS.Services.Controllers
_context.TaskAttachments.Add(attachment); _context.TaskAttachments.Add(attachment);
_logger.LogInfo("Attachment uploaded for Task {TaskId}: {FileName}", approveTask.Id, fileName); _logger.LogInfo("Attachment uploaded for Task {TaskId}: {FileName}", approveTask.Id, fileName);
numberofImages += 1;
} }
} }
// Commit all changes to the database // Commit all changes to the database
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Task_Report", NumberOfImages = numberofImages, ProjectId = projectId };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
_logger.LogInfo("Task {TaskId} successfully approved by Employee {EmployeeId}", approveTask.Id, loggedInEmployee.Id); _logger.LogInfo("Task {TaskId} successfully approved by Employee {EmployeeId}", approveTask.Id, loggedInEmployee.Id);
return Ok(ApiResponse<object>.SuccessResponse("Task has been approved", "Task has been approved", 200)); return Ok(ApiResponse<object>.SuccessResponse("Task has been approved", "Task has been approved", 200));