Compare commits

...

32 Commits

Author SHA1 Message Date
2ed0e6e5b6 Merge branch 'Issues_June_3W' of https://git.marcoaiot.com/admin/marco.pms.api into Issues_June_3W 2025-06-23 17:10:10 +05:30
72a92417b5 Implemented An API to get list of all notes 2025-06-23 17:10:05 +05:30
3dfde6d9a5 Added new entry in status master table 2025-06-19 11:43:10 +05:30
4164c7d761 Cheange the message sending when manageing project infrastructure 2025-06-18 18:12:28 +05:30
303f326773 Implemented signalR in Employee module 2025-06-18 12:00:35 +05:30
e2956c0c8c Added signalR functionality in Project Infrastructure 2025-06-17 17:05:52 +05:30
39378f3a88 Resolved rebase issues 2025-06-16 18:09:00 +05:30
caacb43aa8 Merge branch 'SingalR_Integration' of https://git.marcoaiot.com/admin/marco.pms.api into SingalR_Integration 2025-06-16 17:57:49 +05:30
29ea1698bc Implemented signalR in manage Task API 2025-06-16 17:57:44 +05:30
793877b8f8 Implemented signalR in project infrastructure APIs 2025-06-16 17:57:37 +05:30
f47586710b Add basic implementation and add in record attendance API for testing 2025-06-16 17:55:59 +05:30
b21d30c18e Changed the keyword for updating and creating project 2025-06-16 17:54:52 +05:30
d78a2fe3b2 Implemented signalR in project infrastructure APIs 2025-06-16 17:54:52 +05:30
6ebc74499f Add basic implementation and add in record attendance API for testing 2025-06-16 17:54:52 +05:30
17ae02a0b3 Implemented signalR in manage Task API 2025-06-13 16:22:58 +05:30
9d5535edf1 Added description in list task API 2025-06-12 22:30:43 +05:30
c689f2dfd8 Merge branch 'Acititvity_Images' of https://git.marcoaiot.com/admin/marco.pms.api into Acititvity_Images 2025-06-12 21:40:36 +05:30
5c019a2ff6 Chnaged the name form presignedUrls to ReportedProsignedUrl 2025-06-12 21:40:31 +05:30
cb185db4f3 Sending pre signed url in task list API 2025-06-12 21:40:31 +05:30
1a51860517 Implemented the image capture while writing comment in task allocation and add presignedurl in task get by in API 2025-06-12 21:40:31 +05:30
f275d08215 Implemented image storing to S3 while reporting task 2025-06-12 21:40:31 +05:30
4ccc690560 Chnaged the name form presignedUrls to ReportedProsignedUrl 2025-06-12 21:35:40 +05:30
82f3fdbc23 Merge branch 'SingalR_Integration' of https://git.marcoaiot.com/admin/marco.pms.api into SingalR_Integration 2025-06-12 20:54:06 +05:30
790e9f63e1 Changed the keyword for updating and creating project 2025-06-12 20:54:00 +05:30
0636c8aedd Implemented signalR in project infrastructure APIs 2025-06-12 20:54:00 +05:30
8f2c828282 Add basic implementation and add in record attendance API for testing 2025-06-12 20:54:00 +05:30
169e7f6601 Changed the keyword for updating and creating project 2025-06-12 20:53:24 +05:30
691a670a28 Sending pre signed url in task list API 2025-06-12 18:32:50 +05:30
76b6ac6581 Implemented the image capture while writing comment in task allocation and add presignedurl in task get by in API 2025-06-12 17:02:18 +05:30
abe7870ad5 Implemented image storing to S3 while reporting task 2025-06-12 16:09:28 +05:30
712c5e2a0a Implemented signalR in project infrastructure APIs 2025-06-12 11:04:24 +05:30
8814dc59d9 Add basic implementation and add in record attendance API for testing 2025-06-11 10:21:39 +05:30
27 changed files with 7158 additions and 103 deletions

View File

@ -44,6 +44,7 @@ namespace Marco.Pms.DataAccess.Data
public DbSet<TaskAllocation> TaskAllocations { get; set; }
public DbSet<TaskComment> TaskComments { get; set; }
public DbSet<TaskMembers> TaskMembers { get; set; }
public DbSet<TaskAttachment> TaskAttachments { get; set; }
public DbSet<Attendance> Attendes { get; set; }
public DbSet<AttendanceLog> AttendanceLogs { get; set; }
public DbSet<Employee> Employees { get; set; }
@ -160,14 +161,19 @@ namespace Marco.Pms.DataAccess.Data
},
new StatusMaster
{
Id = new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
Id = new Guid("cdad86aa-8a56-4ff4-b633-9c629057dfef"),
Status = "In Progress",
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
}, new StatusMaster
{
Id = new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
Status = "On Hold",
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
},
new StatusMaster
{
Id = new Guid("ef1c356e-0fe0-42df-a5d3-8daee355492d"),
Status = "On Hold",
Status = "In Active",
TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26")
},
new StatusMaster

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Added_TaskAttachments_Table : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "TaskAttachments",
columns: table => new
{
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
ReferenceId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
DocumentId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
},
constraints: table =>
{
table.PrimaryKey("PK_TaskAttachments", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TaskAttachments");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Added_New_Status_Master_In_Progress : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "StatusMasters",
keyColumn: "Id",
keyValue: new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
column: "Status",
value: "On Hold");
migrationBuilder.UpdateData(
table: "StatusMasters",
keyColumn: "Id",
keyValue: new Guid("ef1c356e-0fe0-42df-a5d3-8daee355492d"),
column: "Status",
value: "In Active");
migrationBuilder.InsertData(
table: "StatusMasters",
columns: new[] { "Id", "Status", "TenantId" },
values: new object[] { new Guid("cdad86aa-8a56-4ff4-b633-9c629057dfef"), "In Progress", new Guid("b3466e83-7e11-464c-b93a-daf047838b26") });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "StatusMasters",
keyColumn: "Id",
keyValue: new Guid("cdad86aa-8a56-4ff4-b633-9c629057dfef"));
migrationBuilder.UpdateData(
table: "StatusMasters",
keyColumn: "Id",
keyValue: new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
column: "Status",
value: "In Progress");
migrationBuilder.UpdateData(
table: "StatusMasters",
keyColumn: "Id",
keyValue: new Guid("ef1c356e-0fe0-42df-a5d3-8daee355492d"),
column: "Status",
value: "On Hold");
}
}
}

View File

@ -63,6 +63,23 @@ namespace Marco.Pms.DataAccess.Migrations
b.ToTable("TaskAllocations");
});
modelBuilder.Entity("Marco.Pms.Model.Activities.TaskAttachment", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid>("DocumentId")
.HasColumnType("char(36)");
b.Property<Guid>("ReferenceId")
.HasColumnType("char(36)");
b.HasKey("Id");
b.ToTable("TaskAttachments");
});
modelBuilder.Entity("Marco.Pms.Model.Activities.TaskComment", b =>
{
b.Property<Guid>("Id")
@ -1658,17 +1675,23 @@ namespace Marco.Pms.DataAccess.Migrations
},
new
{
Id = new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
Id = new Guid("cdad86aa-8a56-4ff4-b633-9c629057dfef"),
Status = "In Progress",
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("ef1c356e-0fe0-42df-a5d3-8daee355492d"),
Id = new Guid("603e994b-a27f-4e5d-a251-f3d69b0498ba"),
Status = "On Hold",
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("ef1c356e-0fe0-42df-a5d3-8daee355492d"),
Status = "In Active",
TenantId = new Guid("b3466e83-7e11-464c-b93a-daf047838b26")
},
new
{
Id = new Guid("33deaef9-9af1-4f2a-b443-681ea0d04f81"),
Status = "Completed",

View File

@ -0,0 +1,9 @@
namespace Marco.Pms.Model.Activities
{
public class TaskAttachment
{
public Guid Id { get; set; }
public Guid ReferenceId { get; set; }
public Guid DocumentId { get; set; }
}
}

View File

@ -1,17 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace Marco.Pms.Model.Activities
{
public class TaskImages
{
public Guid Id { get; set; }
public Guid TaskAllocationId { get; set; }
[ValidateNever]
[ForeignKey(nameof(TaskAllocationId))]
public TaskAllocation? TaskAllocation { get; set; }
public string? ImagePath { get; set; }
}
}

View File

@ -1,11 +1,12 @@
namespace Marco.Pms.Model.Dtos.Activities
using Marco.Pms.Model.Utilities;
namespace Marco.Pms.Model.Dtos.Activities
{
public class CreateCommentDto
{
public Guid TaskAllocationId { get; set; }
public DateTime CommentDate { get; set; }
public string? Comment { get; set; }
public List<FileUploadModel>? Images { get; set; }
}
}

View File

@ -1,4 +1,6 @@
namespace Marco.Pms.Model.Dtos.Activities
using Marco.Pms.Model.Utilities;
namespace Marco.Pms.Model.Dtos.Activities
{
public class ReportTaskDto
{
@ -7,5 +9,6 @@
public DateTime ReportedDate { get; set; }
public string? Comment { get; set; }
public List<ReportCheckListDto>? CheckList { get; set; }
public List<FileUploadModel>? Images { get; set; }
}
}

View File

@ -8,7 +8,7 @@
public List<UpdateContactPhoneDto>? ContactPhones { get; set; }
public List<UpdateContactEmailDto>? ContactEmails { get; set; }
public List<Guid>? BucketIds { get; set; }
public Guid ContactCategoryId { get; set; }
public Guid? ContactCategoryId { get; set; }
public string? Description { get; set; }
public string? Organization { get; set; }
public string? Address { get; set; }

View File

@ -101,6 +101,7 @@ namespace Marco.Pms.Model.Mapper
{
Id = taskAllocation.Id,
AssignmentDate = taskAllocation.AssignmentDate,
Description = taskAllocation.Description,
PlannedTask = taskAllocation.PlannedTask,
ReportedDate = taskAllocation.ReportedDate,
CompletedTask = taskAllocation.CompletedTask,

View File

@ -8,5 +8,6 @@
public string? Comment { get; set; }
public Guid CommentedBy { get; set; }
public BasicEmployeeVM? Employee { get; set; }
public List<string>? PreSignedUrls { get; set; }
}
}

View File

@ -10,7 +10,9 @@ namespace Marco.Pms.Model.ViewModels.Activities
public double PlannedTask { get; set; }
public double CompletedTask { get; set; }
public BasicEmployeeVM? AssignedBy { get; set; }
public string? Description { get; set; }
public Guid WorkItemId { get; set; }
public List<string>? ReportedPreSignedUrls { get; set; }
public WorkItem? WorkItem { get; set; }
public List<BasicEmployeeVM>? teamMembers { get; set; }
public List<CommentVM>? comments { get; set; }

View File

@ -10,7 +10,6 @@
public string? Description { get; set; }
public Guid AssignedBy { get; set; }
public Guid WorkItemId { get; set; }
public List<CommentVM>? Comments { get; set; }
public List<CheckListVM>? checkList { get; set; }
}

View File

@ -13,6 +13,7 @@ namespace Marco.Pms.Model.ViewModels.Activities
public string? Description { get; set; }
public string? AssignBy { get; set; }
public WorkItem? WorkItem { get; set; }
public List<string>? PreSignedUrls { get; set; }
public List<CommentVM>? Comments { get; set; }
public List<EmployeeVM>? TeamMembers { get; set; }
}

View File

@ -7,11 +7,13 @@ using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.AttendanceVM;
using Marco.Pms.Services.Hubs;
using Marco.Pms.Services.Service;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
using Document = Marco.Pms.Model.DocumentManager.Document;
@ -30,10 +32,11 @@ namespace MarcoBMS.Services.Controllers
private readonly S3UploadService _s3Service;
private readonly PermissionServices _permission;
private readonly ILoggingService _logger;
private readonly IHubContext<MarcoHub> _signalR;
public AttendanceController(
ApplicationDbContext context, EmployeeHelper employeeHelper, ProjectsHelper projectsHelper, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permission)
ApplicationDbContext context, EmployeeHelper employeeHelper, ProjectsHelper projectsHelper, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permission, IHubContext<MarcoHub> signalR)
{
_context = context;
_employeeHelper = employeeHelper;
@ -42,6 +45,7 @@ namespace MarcoBMS.Services.Controllers
_s3Service = s3Service;
_logger = logger;
_permission = permission;
_signalR = signalR;
}
private Guid GetTenantId()
@ -558,6 +562,13 @@ namespace MarcoBMS.Services.Controllers
Activity = attendance.Activity,
JobRoleName = employee.JobRole.Name
};
var sendActivity = 0;
if (recordAttendanceDot.Id == Guid.Empty)
{
sendActivity = 1;
}
var notification = new { LoggedInUserId = currentEmployee.Id, Keyword = "Attendance", Activity = sendActivity, ProjectId = attendance.ProjectID, Response = vm };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
_logger.LogInfo("Attendance for employee {FirstName} {LastName} has been marked", employee.FirstName ?? string.Empty, employee.LastName ?? string.Empty);
return Ok(ApiResponse<object>.SuccessResponse(vm, "Attendance marked successfully.", 200));
}
@ -688,7 +699,6 @@ namespace MarcoBMS.Services.Controllers
}
Document? document = null;
var Image = recordAttendanceDot.Image;
var objectKey = string.Empty;
var preSignedUrl = string.Empty;
if (Image != null && Image.ContentType != null)
@ -697,7 +707,16 @@ namespace MarcoBMS.Services.Controllers
if (string.IsNullOrEmpty(Image.Base64Data))
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, TenantId, "attendance");
//If base64 has a data URI prefix, strip it
var base64 = Image.Base64Data.Contains(",")
? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
: Image.Base64Data;
string fileType = _s3Service.GetContentTypeFromBase64(base64);
string fileName = _s3Service.GenerateFileName(fileType, TenantId, "attendance");
string objectKey = $"tenant-{TenantId}/Employee/{recordAttendanceDot.EmployeeID}/Attendance/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey);
preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(objectKey);
document = new Document
@ -712,6 +731,7 @@ namespace MarcoBMS.Services.Controllers
};
_context.Documents.Add(document);
await _context.SaveChangesAsync();
}

View File

@ -9,11 +9,13 @@ using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Employee;
using Marco.Pms.Services.Hubs;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
namespace MarcoBMS.Services.Controllers
@ -32,9 +34,11 @@ namespace MarcoBMS.Services.Controllers
private readonly UserHelper _userHelper;
private readonly IConfiguration _configuration;
private readonly ILoggingService _logger;
private readonly IHubContext<MarcoHub> _signalR;
public EmployeeController(UserManager<ApplicationUser> userManager, IEmailSender emailSender,
ApplicationDbContext context, EmployeeHelper employeeHelper, UserHelper userHelper, IConfiguration configuration, ILoggingService logger)
ApplicationDbContext context, EmployeeHelper employeeHelper, UserHelper userHelper, IConfiguration configuration, ILoggingService logger,
IHubContext<MarcoHub> signalR)
{
_context = context;
_userManager = userManager;
@ -43,6 +47,7 @@ namespace MarcoBMS.Services.Controllers
_userHelper = userHelper;
_configuration = configuration;
_logger = logger;
_signalR = signalR;
}
[HttpGet]
@ -154,6 +159,8 @@ namespace MarcoBMS.Services.Controllers
public async Task<IActionResult> CreateUser([FromBody] CreateUserDto model)
{
Guid tenantId = _userHelper.GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
Guid employeeId = Guid.Empty;
if (model == null)
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid data", "Invaild Data", 400));
@ -180,6 +187,7 @@ namespace MarcoBMS.Services.Controllers
_context.Employees.Update(existingEmployee);
await _context.SaveChangesAsync();
employeeId = existingEmployee.Id;
responsemessage = "User updated successfully.";
}
else
@ -214,7 +222,7 @@ namespace MarcoBMS.Services.Controllers
_context.Employees.Add(newEmployee);
await _context.SaveChangesAsync();
employeeId = newEmployee.Id;
/* SEND USER REGISTRATION MAIL*/
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
@ -233,6 +241,7 @@ namespace MarcoBMS.Services.Controllers
_context.Employees.Update(existingEmployee);
await _context.SaveChangesAsync();
employeeId = existingEmployee.Id;
/* SEND USER REGISTRATION MAIL*/
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
@ -256,17 +265,22 @@ namespace MarcoBMS.Services.Controllers
existingEmployee = GetUpdateEmployeeModel(model, existingEmployee);
_context.Employees.Update(existingEmployee);
responsemessage = "User updated successfully.";
employeeId = existingEmployee.Id;
}
else
{
// Create Employee record if missing
Employee newEmployee = GetNewEmployeeModel(model, tenantId, string.Empty);
_context.Employees.Add(newEmployee);
employeeId = newEmployee.Id;
}
await _context.SaveChangesAsync();
responsemessage = "User created successfully.";
}
var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Employee", EmployeeId = employeeId };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
return Ok(ApiResponse<object>.SuccessResponse("Success.", responsemessage, 200));
}
@ -420,6 +434,9 @@ namespace MarcoBMS.Services.Controllers
}
await _context.SaveChangesAsync();
_logger.LogInfo("Employee with ID {EmployeId} Deleted successfully", employee.Id);
var notification = new { LoggedInUserId = LoggedEmployee.Id, Keyword = "Employee", EmployeeId = employee.Id };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
}
}
else

View File

@ -68,7 +68,16 @@ namespace Marco.Pms.Services.Controllers
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
}
var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
//If base64 has a data URI prefix, strip it
var base64 = Image.Base64Data.Contains(",")
? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
: Image.Base64Data;
string fileType = _s3Service.GetContentTypeFromBase64(base64);
string fileName = _s3Service.GenerateFileName(fileType, tenantId, "forum");
string objectKey = $"tenant-{tenantId}/project-{createTicketDto.LinkedProjectId}/froum/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey);
Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, createTicketDto.CreatedAt, tenantId);
_context.Documents.Add(document);
@ -182,7 +191,16 @@ namespace Marco.Pms.Services.Controllers
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
}
var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
//If base64 has a data URI prefix, strip it
var base64 = Image.Base64Data.Contains(",")
? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
: Image.Base64Data;
string fileType = _s3Service.GetContentTypeFromBase64(base64);
string fileName = _s3Service.GenerateFileName(fileType, tenantId, "forum");
string objectKey = $"tenant-{tenantId}/project-{updateTicketDto.LinkedProjectId}/froum/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey);
Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, updateTicketDto.CreatedAt, tenantId);
_context.Documents.Add(document);
@ -329,6 +347,14 @@ namespace Marco.Pms.Services.Controllers
List<TicketAttachment> attachments = new List<TicketAttachment>();
List<Document> documents = new List<Document>();
TicketForum? ticket = await _context.Tickets.FirstOrDefaultAsync(t => t.Id == addCommentDto.TicketId);
if (ticket == null)
{
_logger.LogError("Ticket {TicketId} not Found in database", addCommentDto.TicketId);
return NotFound(ApiResponse<object>.ErrorResponse("Ticket not Found", "Ticket not Found", 404));
}
TicketComment comment = addCommentDto.ToTicketCommentFromAddCommentDto(tenantId);
_context.TicketComments.Add(comment);
await _context.SaveChangesAsync();
@ -344,7 +370,16 @@ namespace Marco.Pms.Services.Controllers
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
}
var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
//If base64 has a data URI prefix, strip it
var base64 = Image.Base64Data.Contains(",")
? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
: Image.Base64Data;
string fileType = _s3Service.GetContentTypeFromBase64(base64);
string fileName = _s3Service.GenerateFileName(fileType, tenantId, "forum");
string objectKey = $"tenant-{tenantId}/project-{ticket.LinkedProjectId}/froum/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey);
Document document = attachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, addCommentDto.SentAt, tenantId);
_context.Documents.Add(document);
@ -396,6 +431,14 @@ namespace Marco.Pms.Services.Controllers
Guid tenantId = _userHelper.GetTenantId();
List<TicketAttachment> attachments = new List<TicketAttachment>();
TicketForum? ticket = await _context.Tickets.FirstOrDefaultAsync(t => t.Id == updateCommentDto.TicketId);
if (ticket == null)
{
_logger.LogError("Ticket {TicketId} not Found in database", updateCommentDto.TicketId);
return NotFound(ApiResponse<object>.ErrorResponse("Ticket not Found", "Ticket not Found", 404));
}
TicketComment existingComment = await _context.TicketComments.AsNoTracking().FirstOrDefaultAsync(c => c.Id == updateCommentDto.Id) ?? new TicketComment();
TicketComment updateComment = updateCommentDto.ToTicketCommentFromUpdateCommentDto(tenantId, existingComment);
_context.TicketComments.Update(updateComment);
@ -419,7 +462,16 @@ namespace Marco.Pms.Services.Controllers
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
}
var objectKey = await _s3Service.UploadFileAsync(Image.Base64Data, tenantId, "forum");
//If base64 has a data URI prefix, strip it
var base64 = Image.Base64Data.Contains(",")
? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
: Image.Base64Data;
string fileType = _s3Service.GetContentTypeFromBase64(base64);
string fileName = _s3Service.GenerateFileName(fileType, tenantId, "forum");
string objectKey = $"tenant-{tenantId}/project-{ticket.LinkedProjectId}/froum/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey);
Document document = attachmentDto.ToDocumentFromUpdateAttachmentDto(objectKey, objectKey, existingComment.SentAt, tenantId);
_context.Documents.Add(document);
@ -491,6 +543,16 @@ namespace Marco.Pms.Services.Controllers
Guid tenantId = _userHelper.GetTenantId();
List<TicketAttachmentVM> ticketAttachmentVMs = new List<TicketAttachmentVM>();
List<Guid> ticketIds = forumAttachmentDtos.Select(f => f.TicketId.HasValue ? f.TicketId.Value : Guid.Empty).ToList();
List<TicketForum> tickets = await _context.Tickets.Where(t => ticketIds.Contains(t.Id)).ToListAsync();
if (tickets == null || tickets.Count > 0)
{
_logger.LogError("Tickets not Found in database");
return NotFound(ApiResponse<object>.ErrorResponse("Ticket not Found", "Ticket not Found", 404));
}
TicketAttachment attachment = new TicketAttachment();
foreach (var forumAttachmentDto in forumAttachmentDtos)
@ -505,7 +567,17 @@ namespace Marco.Pms.Services.Controllers
_logger.LogError("ticket ID is missing");
return BadRequest(ApiResponse<object>.ErrorResponse("ticket ID is missing", "ticket ID is missing", 400));
}
var objectKey = await _s3Service.UploadFileAsync(forumAttachmentDto.Base64Data, tenantId, "forum");
var ticket = tickets.FirstOrDefault(t => t.Id == forumAttachmentDto.TicketId);
//If base64 has a data URI prefix, strip it
var base64 = forumAttachmentDto.Base64Data.Contains(",")
? forumAttachmentDto.Base64Data.Substring(forumAttachmentDto.Base64Data.IndexOf(",") + 1)
: forumAttachmentDto.Base64Data;
string fileType = _s3Service.GetContentTypeFromBase64(base64);
string fileName = _s3Service.GenerateFileName(fileType, tenantId, "forum");
string objectKey = $"tenant-{tenantId}/project-{ticket?.LinkedProjectId}/froum/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey);
Document document = forumAttachmentDto.ToDocumentFromForumAttachmentDto(objectKey, objectKey, forumAttachmentDto.SentAt, tenantId);
_context.Documents.Add(document);

View File

@ -749,11 +749,11 @@ namespace Marco.Pms.Services.Controllers
return Ok(response);
}
[HttpGet("contact-tag/{id}")]
public async Task<IActionResult> GetContactTagMaster(Guid id)
{
return Ok();
}
//[HttpGet("contact-tag/{id}")]
//public async Task<IActionResult> GetContactTagMaster(Guid id)
//{
// return Ok();
//}
[HttpPost("contact-tag")]
public async Task<IActionResult> CreateContactTagMaster([FromBody] CreateContactTagDto contactTagDto)

View File

@ -8,12 +8,13 @@ using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Employee;
using Marco.Pms.Model.ViewModels.Projects;
using Marco.Pms.Services.Hubs;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace MarcoBMS.Services.Controllers
{
@ -27,15 +28,18 @@ namespace MarcoBMS.Services.Controllers
private readonly ILoggingService _logger;
private readonly RolesHelper _rolesHelper;
private readonly ProjectsHelper _projectsHelper;
private readonly IHubContext<MarcoHub> _signalR;
public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper)
public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper, IHubContext<MarcoHub> signalR)
{
_context = context;
_userHelper = userHelper;
_logger = logger;
_rolesHelper = rolesHelper;
_projectsHelper = projectHelper;
_signalR = signalR;
}
[HttpGet("list/basic")]
@ -59,9 +63,9 @@ namespace MarcoBMS.Services.Controllers
return Unauthorized(ApiResponse<object>.ErrorResponse("Employee not found.", null, 401));
}
List<Project> projects = await _projectsHelper.GetMyProjects(tenantId, LoggedInEmployee);
List<Project> projects = await _projectsHelper.GetMyProjects(tenantId, LoggedInEmployee);
// 4. Project projection to ProjectInfoVM
// This part is already quite efficient.
@ -84,8 +88,6 @@ namespace MarcoBMS.Services.Controllers
return Ok(ApiResponse<object>.SuccessResponse(response, "Success.", 200));
}
[HttpGet("list")]
public async Task<IActionResult> GetAll()
{
@ -314,6 +316,7 @@ namespace MarcoBMS.Services.Controllers
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateProjectDto projectDto)
{
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
if (!ModelState.IsValid)
{
var errors = ModelState.Values
@ -330,6 +333,9 @@ namespace MarcoBMS.Services.Controllers
_context.Projects.Add(project);
await _context.SaveChangesAsync();
var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Create_Project", Response = project.ToProjectDto() };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
return Ok(ApiResponse<object>.SuccessResponse(project.ToProjectDto(), "Success.", 200));
}
@ -338,6 +344,7 @@ namespace MarcoBMS.Services.Controllers
[Route("update/{id}")]
public async Task<IActionResult> Update([FromRoute] Guid id, [FromBody] UpdateProjectDto updateProjectDto)
{
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
if (!ModelState.IsValid)
{
var errors = ModelState.Values
@ -356,6 +363,10 @@ namespace MarcoBMS.Services.Controllers
await _context.SaveChangesAsync();
var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Update_Project", Response = project.ToProjectDto() };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
return Ok(ApiResponse<object>.SuccessResponse(project.ToProjectDto(), "Success.", 200));
}
@ -365,7 +376,6 @@ namespace MarcoBMS.Services.Controllers
}
}
//[HttpPost("assign-employee")]
//public async Task<IActionResult> AssignEmployee(int? allocationid, int employeeId, int projectId)
//{
@ -506,7 +516,11 @@ namespace MarcoBMS.Services.Controllers
if (projectAllocationDot != null)
{
Guid TenentID = GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
List<object>? result = new List<object>();
List<Guid> employeeIds = new List<Guid>();
List<Guid> projectIds = new List<Guid>();
foreach (var item in projectAllocationDot)
{
@ -535,6 +549,9 @@ namespace MarcoBMS.Services.Controllers
projectAllocationFromDb.IsActive = false;
_context.Entry(projectAllocationFromDb).Property(e => e.ReAllocationDate).IsModified = true;
_context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true;
employeeIds.Add(projectAllocation.EmployeeId);
projectIds.Add(projectAllocation.ProjectId);
}
await _context.SaveChangesAsync();
var result1 = new
@ -556,15 +573,21 @@ namespace MarcoBMS.Services.Controllers
projectAllocation.IsActive = true;
_context.ProjectAllocations.Add(projectAllocation);
await _context.SaveChangesAsync();
employeeIds.Add(projectAllocation.EmployeeId);
projectIds.Add(projectAllocation.ProjectId);
}
}
catch (Exception ex)
{
return Ok(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400));
}
}
var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Assign_Project", ProjectIds = projectIds, EmployeeList = employeeIds };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
return Ok(ApiResponse<object>.SuccessResponse(result, "Data saved successfully", 200));
}
@ -576,7 +599,10 @@ namespace MarcoBMS.Services.Controllers
public async Task<IActionResult> CreateProjectTask(List<WorkItemDot> workItemDot)
{
Guid tenantId = GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
List<WorkItemVM> workItems = new List<WorkItemVM> { };
List<Guid> projectIds = new List<Guid>();
string message = "";
string responseMessage = "";
if (workItemDot != null)
{
@ -584,19 +610,25 @@ namespace MarcoBMS.Services.Controllers
{
WorkItem workItem = item.ToWorkItemFromWorkItemDto(tenantId);
var workArea = await _context.WorkAreas.Include(a => a.Floor).FirstOrDefaultAsync(a => a.Id == workItem.WorkAreaId) ?? new WorkArea();
Building building = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == (workArea.Floor != null ? workArea.Floor.BuildingId : Guid.Empty)) ?? new Building();
if (item.Id != null)
{
//update
_context.WorkItems.Update(workItem);
await _context.SaveChangesAsync();
responseMessage = "Task Added Successfully";
responseMessage = "Task Updated Successfully";
message = $"Task Updated in Building: {building.Name}, on Floor: {workArea.Floor?.FloorName}, in Area: {workArea.AreaName} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}";
}
else
{
//create
_context.WorkItems.Add(workItem);
await _context.SaveChangesAsync();
responseMessage = "Task Updated Successfully";
responseMessage = "Task Added Successfully";
message = $"Task Added in Building: {building.Name}, on Floor: {workArea.Floor?.FloorName}, in Area: {workArea.AreaName} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}";
}
var result = new WorkItemVM
{
@ -604,9 +636,14 @@ namespace MarcoBMS.Services.Controllers
WorkItem = workItem
};
workItems.Add(result);
projectIds.Add(building.ProjectId);
}
var activity = await _context.ActivityMasters.ToListAsync();
var category = await _context.WorkCategoryMasters.ToListAsync();
var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Infra", ProjectIds = projectIds, Message = message };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
return Ok(ApiResponse<object>.SuccessResponse(workItems, responseMessage, 200));
}
@ -618,7 +655,9 @@ namespace MarcoBMS.Services.Controllers
public async Task<IActionResult> DeleteProjectTask(Guid id)
{
Guid tenantId = _userHelper.GetTenantId();
WorkItem? task = await _context.WorkItems.AsNoTracking().FirstOrDefaultAsync(t => t.Id == id && t.TenantId == tenantId);
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
List<Guid> projectIds = new List<Guid>();
WorkItem? task = await _context.WorkItems.AsNoTracking().Include(t => t.WorkArea).FirstOrDefaultAsync(t => t.Id == id && t.TenantId == tenantId);
if (task != null)
{
if (task.CompletedWork == 0)
@ -629,6 +668,15 @@ namespace MarcoBMS.Services.Controllers
_context.WorkItems.Remove(task);
await _context.SaveChangesAsync();
_logger.LogInfo("Task with ID {WorkItemId} has been successfully deleted.", task.Id);
var floorId = task.WorkArea?.FloorId;
var floor = await _context.Floor.Include(f => f.Building).FirstOrDefaultAsync(f => f.Id == floorId);
projectIds.Add(floor?.Building?.ProjectId ?? Guid.Empty);
var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Infra", ProjectIds = projectIds, Message = $"Task Deleted in Building: {floor?.Building?.Name}, on Floor: {floor?.FloorName}, in Area: {task.WorkArea?.AreaName} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}" };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
}
else
{
@ -656,8 +704,12 @@ namespace MarcoBMS.Services.Controllers
public async Task<IActionResult> ManageProjectInfra(List<InfraDot> infraDots)
{
Guid tenantId = GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var responseData = new InfraVM { };
string responseMessage = "";
string message = "";
List<Guid> projectIds = new List<Guid>();
if (infraDots != null)
{
foreach (var item in infraDots)
@ -675,6 +727,7 @@ namespace MarcoBMS.Services.Controllers
await _context.SaveChangesAsync();
responseData.building = building;
responseMessage = "Buliding Added Successfully";
message = "Building Added";
}
else
{
@ -683,8 +736,10 @@ namespace MarcoBMS.Services.Controllers
await _context.SaveChangesAsync();
responseData.building = building;
responseMessage = "Buliding Updated Successfully";
message = "Building Updated";
}
projectIds.Add(building.ProjectId);
}
if (item.Floor != null)
{
@ -698,6 +753,7 @@ namespace MarcoBMS.Services.Controllers
await _context.SaveChangesAsync();
responseData.floor = floor;
responseMessage = "Floor Added Successfully";
message = "Floor Added";
}
else
{
@ -706,7 +762,11 @@ namespace MarcoBMS.Services.Controllers
await _context.SaveChangesAsync();
responseData.floor = floor;
responseMessage = "Floor Updated Successfully";
message = "Floor Updated";
}
Building? building = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == floor.BuildingId);
projectIds.Add(building?.ProjectId ?? Guid.Empty);
message = $"{message} in Building: {building?.Name}";
}
if (item.WorkArea != null)
{
@ -720,6 +780,7 @@ namespace MarcoBMS.Services.Controllers
await _context.SaveChangesAsync();
responseData.workArea = workArea;
responseMessage = "Work Area Added Successfully";
message = "Work Area Added";
}
else
{
@ -728,9 +789,17 @@ namespace MarcoBMS.Services.Controllers
await _context.SaveChangesAsync();
responseData.workArea = workArea;
responseMessage = "Work Area Updated Successfully";
message = "Work Area Updated";
}
Floor? floor = await _context.Floor.Include(f => f.Building).FirstOrDefaultAsync(f => f.Id == workArea.FloorId);
projectIds.Add(floor?.Building?.ProjectId ?? Guid.Empty);
message = $"{message} in Building: {floor?.Building?.Name}, on Floor: {floor?.FloorName}";
}
}
message = $"{message} by {LoggedInEmployee.FirstName} {LoggedInEmployee.LastName}";
var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Infra", ProjectIds = projectIds, Message = message };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
return Ok(ApiResponse<object>.SuccessResponse(responseData, responseMessage, 200));
}
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid details.", "Infra Details are not valid.", 400));
@ -776,16 +845,15 @@ namespace MarcoBMS.Services.Controllers
return Ok(ApiResponse<object>.SuccessResponse(projects, "Success.", 200));
}
[HttpPost("assign-projects/{employeeId}")]
public async Task<ActionResult> AssigneProjectsToEmployee([FromBody] List<ProjectsAllocationDto> projectAllocationDtos, [FromRoute] Guid employeeId)
{
if (projectAllocationDtos != null && employeeId != Guid.Empty)
{
Guid TenentID = GetTenantId();
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
List<object>? result = new List<object>();
List<Guid> projectIds = new List<Guid>();
foreach (var projectAllocationDto in projectAllocationDtos)
{
@ -813,6 +881,8 @@ namespace MarcoBMS.Services.Controllers
projectAllocationFromDb.IsActive = false;
_context.Entry(projectAllocationFromDb).Property(e => e.ReAllocationDate).IsModified = true;
_context.Entry(projectAllocationFromDb).Property(e => e.IsActive).IsModified = true;
projectIds.Add(projectAllocation.ProjectId);
}
await _context.SaveChangesAsync();
var result1 = new
@ -835,6 +905,8 @@ namespace MarcoBMS.Services.Controllers
_context.ProjectAllocations.Add(projectAllocation);
await _context.SaveChangesAsync();
projectIds.Add(projectAllocation.ProjectId);
}
@ -845,6 +917,9 @@ namespace MarcoBMS.Services.Controllers
return Ok(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400));
}
}
var notification = new { LoggedInUserId = LoggedInEmployee.Id, Keyword = "Assign_Project", ProjectIds = projectIds, EmployeeId = employeeId };
await _signalR.Clients.All.SendAsync("NotificationEventHandler", notification);
return Ok(ApiResponse<object>.SuccessResponse(result, "Data saved successfully", 200));
}

View File

@ -8,10 +8,13 @@ using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.Employee;
using Marco.Pms.Services.Service;
using MarcoBMS.Services.Helpers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
using Document = Marco.Pms.Model.DocumentManager.Document;
namespace MarcoBMS.Services.Controllers
{
@ -23,12 +26,14 @@ namespace MarcoBMS.Services.Controllers
{
private readonly ApplicationDbContext _context;
private readonly UserHelper _userHelper;
private readonly S3UploadService _s3Service;
public TaskController(ApplicationDbContext context, UserHelper userHelper)
public TaskController(ApplicationDbContext context, UserHelper userHelper, S3UploadService s3Service)
{
_context = context;
_userHelper = userHelper;
_s3Service = s3Service;
}
private Guid GetTenantId()
@ -84,7 +89,6 @@ namespace MarcoBMS.Services.Controllers
return Ok(ApiResponse<object>.SuccessResponse(response, "Task assignned successfully", 200));
}
[HttpPost("report")]
public async Task<IActionResult> ReportTaskProgress([FromBody] ReportTaskDto reportTask)
{
@ -104,10 +108,13 @@ namespace MarcoBMS.Services.Controllers
var checkListIds = reportTask.CheckList != null ? reportTask.CheckList.Select(c => c.Id).ToList() : new List<Guid>();
var checkList = await _context.ActivityCheckLists.Where(c => checkListIds.Contains(c.Id)).ToListAsync();
if (taskAllocation == null)
if (taskAllocation == null || taskAllocation.WorkItem == null)
{
return BadRequest(ApiResponse<object>.ErrorResponse("No such task has been allocated.", "No such task has been allocated.", 400));
}
WorkArea workArea = await _context.WorkAreas.Include(a => a.Floor).FirstOrDefaultAsync(a => a.Id == taskAllocation.WorkItem.WorkAreaId) ?? new WorkArea();
var bulding = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == (workArea.Floor != null ? workArea.Floor.BuildingId : Guid.Empty));
if (taskAllocation.WorkItem != null)
{
if (taskAllocation.CompletedTask != 0)
@ -143,6 +150,49 @@ namespace MarcoBMS.Services.Controllers
_context.CheckListMappings.AddRange(checkListMappings);
var comment = reportTask.ToCommentFromReportTaskDto(tenantId, Employee.Id);
var Images = reportTask.Images;
if (Images != null && Images.Count > 0)
{
foreach (var Image in Images)
{
if (string.IsNullOrEmpty(Image.Base64Data))
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
//If base64 has a data URI prefix, strip it
var base64 = Image.Base64Data.Contains(",")
? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
: Image.Base64Data;
string fileType = _s3Service.GetContentTypeFromBase64(base64);
string fileName = _s3Service.GenerateFileName(fileType, tenantId, "task_report");
string objectKey = $"tenant-{tenantId}/project-{bulding?.ProjectId}/Actitvity/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey);
Document document = new Document
{
FileName = Image.FileName ?? "",
ContentType = Image.ContentType ?? "",
S3Key = objectKey,
Base64Data = Image.Base64Data,
FileSize = Image.FileSize,
UploadedAt = DateTime.UtcNow,
TenantId = tenantId
};
_context.Documents.Add(document);
TaskAttachment attachment = new TaskAttachment
{
DocumentId = document.Id,
ReferenceId = reportTask.Id
};
_context.TaskAttachments.Add(attachment);
}
await _context.SaveChangesAsync();
}
_context.TaskComments.Add(comment);
await _context.SaveChangesAsync();
@ -164,10 +214,62 @@ namespace MarcoBMS.Services.Controllers
var tenantId = GetTenantId();
var Employee = await _userHelper.GetCurrentEmployeeAsync();
var taskAllocation = await _context.TaskAllocations.Include(t => t.WorkItem).FirstOrDefaultAsync(t => t.Id == createComment.TaskAllocationId);
if (taskAllocation == null || taskAllocation.WorkItem == null)
{
return BadRequest(ApiResponse<object>.ErrorResponse("No such task has been allocated.", "No such task has been allocated.", 400));
}
WorkArea workArea = await _context.WorkAreas.Include(a => a.Floor).FirstOrDefaultAsync(a => a.Id == taskAllocation.WorkItem.WorkAreaId) ?? new WorkArea();
var bulding = await _context.Buildings.FirstOrDefaultAsync(b => b.Id == (workArea.Floor != null ? workArea.Floor.BuildingId : Guid.Empty));
var comment = createComment.ToCommentFromCommentDto(tenantId, Employee.Id);
_context.TaskComments.Add(comment);
await _context.SaveChangesAsync();
var Images = createComment.Images;
if (Images != null && Images.Count > 0)
{
foreach (var Image in Images)
{
if (string.IsNullOrEmpty(Image.Base64Data))
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
//If base64 has a data URI prefix, strip it
var base64 = Image.Base64Data.Contains(",")
? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
: Image.Base64Data;
string fileType = _s3Service.GetContentTypeFromBase64(base64);
string fileName = _s3Service.GenerateFileName(fileType, tenantId, "task_report");
string objectKey = $"tenant-{tenantId}/project-{bulding?.ProjectId}/Actitvity/{fileName}";
await _s3Service.UploadFileAsync(base64, fileType, objectKey);
Document document = new Document
{
FileName = Image.FileName ?? "",
ContentType = Image.ContentType ?? "",
S3Key = objectKey,
Base64Data = Image.Base64Data,
FileSize = Image.FileSize,
UploadedAt = DateTime.UtcNow,
TenantId = tenantId
};
_context.Documents.Add(document);
TaskAttachment attachment = new TaskAttachment
{
DocumentId = document.Id,
ReferenceId = comment.Id
};
_context.TaskAttachments.Add(attachment);
}
await _context.SaveChangesAsync();
}
CommentVM response = comment.ToCommentVMFromTaskComment();
return Ok(ApiResponse<object>.SuccessResponse(response, "Comment saved successfully", 200));
}
@ -215,6 +317,13 @@ namespace MarcoBMS.Services.Controllers
List<Employee> employees = await _context.Employees.Where(e => employeeIdList.Contains(e.Id)).Include(e => e.JobRole).ToListAsync();
List<TaskComment> allComments = await _context.TaskComments.Include(c => c.Employee).Where(c => taskIdList.Contains(c.TaskAllocationId)).ToListAsync();
var allCommentIds = allComments.Select(c => c.Id).ToList();
var taskAttachments = await _context.TaskAttachments.Where(t => taskIdList.Contains(t.ReferenceId) || allCommentIds.Contains(t.ReferenceId)).ToListAsync();
var documentIds = taskAttachments.Select(t => t.DocumentId).ToList();
var documents = await _context.Documents.Where(d => documentIds.Contains(d.Id)).ToListAsync();
List<ListTaskVM> tasks = new List<ListTaskVM>();
//foreach (var workItem in workItems)
//{
@ -223,10 +332,21 @@ namespace MarcoBMS.Services.Controllers
var response = taskAllocation.ToListTaskVMFromTaskAllocation();
List<TaskComment> comments = await _context.TaskComments.Where(c => c.TaskAllocationId == taskAllocation.Id).ToListAsync();
List<TaskComment> comments = allComments.Where(c => c.TaskAllocationId == taskAllocation.Id).ToList();
List<BasicEmployeeVM> team = new List<BasicEmployeeVM>();
List<TaskMembers> taskMembers = teamMembers.Where(m => m.TaskAllocationId == taskAllocation.Id).ToList();
var taskDocumentIds = taskAttachments.Where(t => t.ReferenceId == taskAllocation.Id).Select(t => t.DocumentId).ToList();
var taskDocuments = documents.Where(d => taskDocumentIds.Contains(d.Id)).ToList();
List<string> taskPreSignedUrls = new List<string>();
foreach (var document in taskDocuments)
{
string preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key);
taskPreSignedUrls.Add(preSignedUrl);
}
response.ReportedPreSignedUrls = taskPreSignedUrls;
foreach (var taskMember in taskMembers)
{
var teamMember = employees.Find(e => e.Id == taskMember.EmployeeId);
@ -238,7 +358,18 @@ namespace MarcoBMS.Services.Controllers
List<CommentVM> commentVM = new List<CommentVM> { };
foreach (var comment in comments)
{
commentVM.Add(comment.ToCommentVMFromTaskComment());
var commentDocumentIds = taskAttachments.Where(t => t.ReferenceId == comment.Id).Select(t => t.DocumentId).ToList();
var commentDocuments = documents.Where(d => commentDocumentIds.Contains(d.Id)).ToList();
List<string> commentPreSignedUrls = new List<string>();
foreach (var document in commentDocuments)
{
string preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key);
commentPreSignedUrls.Add(preSignedUrl);
}
CommentVM commentVm = comment.ToCommentVMFromTaskComment();
commentVm.PreSignedUrls = commentPreSignedUrls;
commentVM.Add(commentVm);
}
List<ActivityCheckList> checkLists = await _context.ActivityCheckLists.Where(x => x.ActivityId == (taskAllocation.WorkItem != null ? taskAllocation.WorkItem.ActivityId : Guid.Empty)).ToListAsync();
List<CheckListMappings> checkListMappings = await _context.CheckListMappings.Where(c => c.TaskAllocationId == taskAllocation.Id).ToListAsync();
@ -280,8 +411,23 @@ namespace MarcoBMS.Services.Controllers
if (taskAllocation == null) return NotFound(ApiResponse<object>.ErrorResponse("Task Not Found", "Task not found", 404));
var taskVM = taskAllocation.TaskAllocationToTaskVM(employeeName);
var comments = await _context.TaskComments.Where(c => c.TaskAllocationId == taskAllocation.Id).ToListAsync();
var commentIds = comments.Select(c => c.Id).ToList();
var taskAttachments = await _context.TaskAttachments.Where(t => t.ReferenceId == taskAllocation.Id || commentIds.Contains(t.ReferenceId)).ToListAsync();
var documentIds = taskAttachments.Select(t => t.DocumentId).ToList();
var documents = await _context.Documents.Where(d => documentIds.Contains(d.Id)).ToListAsync();
var team = await _context.TaskMembers.Where(m => m.TaskAllocationId == taskAllocation.Id).Include(m => m.Employee).ToListAsync();
var teamMembers = new List<EmployeeVM> { };
var taskDocumentIds = taskAttachments.Where(t => t.ReferenceId == taskAllocation.Id).Select(t => t.DocumentId).ToList();
var taskDocuments = documents.Where(d => taskDocumentIds.Contains(d.Id)).ToList();
List<string> taskPreSignedUrls = new List<string>();
foreach (var document in taskDocuments)
{
string preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key);
taskPreSignedUrls.Add(preSignedUrl);
}
taskVM.PreSignedUrls = taskPreSignedUrls;
foreach (var member in team)
{
var result = member.Employee != null ? member.Employee.ToEmployeeVMFromEmployee() : new EmployeeVM();
@ -290,7 +436,17 @@ namespace MarcoBMS.Services.Controllers
List<CommentVM> Comments = new List<CommentVM> { };
foreach (var comment in comments)
{
Comments.Add(comment.ToCommentVMFromTaskComment());
var commentDocumentIds = taskAttachments.Where(t => t.ReferenceId == comment.Id).Select(t => t.DocumentId).ToList();
var commentDocuments = documents.Where(d => commentDocumentIds.Contains(d.Id)).ToList();
List<string> commentPreSignedUrls = new List<string>();
foreach (var document in commentDocuments)
{
string preSignedUrl = _s3Service.GeneratePreSignedUrlAsync(document.S3Key);
commentPreSignedUrls.Add(preSignedUrl);
}
CommentVM commentVM = comment.ToCommentVMFromTaskComment();
commentVM.PreSignedUrls = commentPreSignedUrls;
Comments.Add(commentVM);
}
taskVM.Comments = Comments;
taskVM.TeamMembers = teamMembers;

View File

@ -0,0 +1,35 @@
using MarcoBMS.Services.Service;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
namespace Marco.Pms.Services.Hubs
{
[Authorize]
public class MarcoHub : Hub
{
private readonly ILoggingService _logger;
public MarcoHub(ILoggingService logger)
{
_logger = logger;
}
public async Task SendMessage(string user, string message)
{
_logger.LogInfo($"User: {user} Message : {message}");
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
_logger.LogInfo($"Connected successfully");
await Clients.All.SendAsync("Connected successfully");
}
// Optional: override OnDisconnectedAsync
public override async Task OnDisconnectedAsync(Exception? exception)
{
await base.OnDisconnectedAsync(exception);
_logger.LogInfo($"DIsonnected successfully");
await Clients.All.SendAsync("Disonnected successfully");
}
}
}

View File

@ -17,6 +17,7 @@
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.12" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.12" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.7" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.12">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@ -4,6 +4,7 @@ using Marco.Pms.Model.Authentication;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Services.Helpers;
using Marco.Pms.Services.Hubs;
using Marco.Pms.Services.Service;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Middleware;
@ -59,7 +60,8 @@ builder.Services.AddCors(options =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
.AllowAnyHeader()
.WithExposedHeaders("Authorization");
});
});
@ -161,10 +163,28 @@ if (jwtSettings != null && jwtSettings.Key != null)
ValidAudience = jwtSettings.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Key))
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
// Match your hub route here
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs/marco"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
builder.Services.AddSingleton(jwtSettings);
}
builder.Services.AddSignalR();
builder.WebHost.ConfigureKestrel(options =>
{
options.AddServerHeader = false; // Disable the "Server" header
@ -207,7 +227,7 @@ app.UseHttpsRedirection();
app.UseAuthorization();
app.MapHub<MarcoHub>("/hubs/marco");
app.MapControllers();
app.Run();

View File

@ -28,53 +28,46 @@ namespace Marco.Pms.Services.Service
_s3Client = new AmazonS3Client(settings.AccessKey, settings.SecretKey, region);
}
//public async Task<string> UploadFileAsync(string fileName, string contentType)
public async Task<string> UploadFileAsync(string base64Data, Guid tenantId, string tag)
public async Task UploadFileAsync(string base64, string fileType, string objectKey)
{
byte[] fileBytes;
//If base64 has a data URI prefix, strip it
var base64 = base64Data.Contains(",")
? base64Data.Substring(base64Data.IndexOf(",") + 1)
: base64Data;
var allowedFilesType = _configuration.GetSection("WhiteList:ContentType")
.GetChildren()
.Select(x => x.Value)
.ToList();
string fileType = GetContentTypeFromBase64(base64);
if (allowedFilesType != null && allowedFilesType.Contains(fileType))
if (allowedFilesType == null || !allowedFilesType.Contains(fileType))
{
string fileName = GenerateFileName(fileType, tenantId, tag);
fileBytes = Convert.FromBase64String(base64);
using var fileStream = new MemoryStream(fileBytes);
// Generate a unique object key (you can customize this)
var objectKey = $"{fileName}";
var uploadRequest = new TransferUtilityUploadRequest
{
InputStream = fileStream,
Key = objectKey,
BucketName = _bucketName,
ContentType = fileType,
AutoCloseStream = true
};
try
{
var transferUtility = new TransferUtility(_s3Client);
await transferUtility.UploadAsync(uploadRequest);
_logger.LogInfo("File uploaded to Amazon S3");
return objectKey;
}
catch (Exception ex)
{
_logger.LogError("{error} while uploading file to S3", ex.Message);
return string.Empty;
}
throw new InvalidOperationException("Unsupported file type.");
}
throw new InvalidOperationException("Unsupported file type.");
fileBytes = Convert.FromBase64String(base64);
using var fileStream = new MemoryStream(fileBytes);
var uploadRequest = new TransferUtilityUploadRequest
{
InputStream = fileStream,
Key = objectKey,
BucketName = _bucketName,
ContentType = fileType,
AutoCloseStream = true
};
try
{
var transferUtility = new TransferUtility(_s3Client);
await transferUtility.UploadAsync(uploadRequest);
_logger.LogInfo("File uploaded to Amazon S3");
}
catch (Exception ex)
{
_logger.LogError("{error} while uploading file to S3", ex.Message);
}
}
public string GeneratePreSignedUrlAsync(string objectKey)
{

View File

@ -9,7 +9,7 @@
"Title": "Dev"
},
"ConnectionStrings": {
"DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMSGuid"
"DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMS1"
},
"SmtpSettings": {
"SmtpServer": "smtp.gmail.com",