Ashutosh_Bug#108_Attendance_log #25

Merged
admin merged 2 commits from Ashutosh_Bug#108_Attendance_log into Feature_Forum 2025-04-25 11:03:30 +00:00
11 changed files with 2931 additions and 101 deletions
Showing only changes of commit 5ca7d721fc - Show all commits

View File

@ -0,0 +1,64 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Made_ComentId_Nullable_In_TicketAttachment : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_TicketAttachments_TicketComments_CommentId",
table: "TicketAttachments");
migrationBuilder.AlterColumn<Guid>(
name: "CommentId",
table: "TicketAttachments",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci",
oldClrType: typeof(Guid),
oldType: "char(36)")
.OldAnnotation("Relational:Collation", "ascii_general_ci");
migrationBuilder.AddForeignKey(
name: "FK_TicketAttachments_TicketComments_CommentId",
table: "TicketAttachments",
column: "CommentId",
principalTable: "TicketComments",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_TicketAttachments_TicketComments_CommentId",
table: "TicketAttachments");
migrationBuilder.AlterColumn<Guid>(
name: "CommentId",
table: "TicketAttachments",
type: "char(36)",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
collation: "ascii_general_ci",
oldClrType: typeof(Guid),
oldType: "char(36)",
oldNullable: true)
.OldAnnotation("Relational:Collation", "ascii_general_ci");
migrationBuilder.AddForeignKey(
name: "FK_TicketAttachments_TicketComments_CommentId",
table: "TicketAttachments",
column: "CommentId",
principalTable: "TicketComments",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

View File

@ -1157,7 +1157,7 @@ namespace Marco.Pms.DataAccess.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("char(36)");
b.Property<Guid>("CommentId")
b.Property<Guid?>("CommentId")
.HasColumnType("char(36)");
b.Property<Guid>("FileId")
@ -2372,9 +2372,7 @@ namespace Marco.Pms.DataAccess.Migrations
{
b.HasOne("Marco.Pms.Model.Forum.TicketComment", "TicketComment")
.WithMany("Attachments")
.HasForeignKey("CommentId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
.HasForeignKey("CommentId");
b.HasOne("Marco.Pms.Model.Forum.TicketForum", "Ticket")
.WithMany()

View File

@ -0,0 +1,28 @@
using Marco.Pms.Model.AttendanceModule;
using Marco.Pms.Model.ViewModels.AttendanceVM;
namespace Marco.Pms.Model.Mapper
{
public static class AttendanceMapper
{
public static AttendanceLogVM ToAttendanceLogVMFromAttendanceLog(this AttendanceLog attendanceLog, string preSignedUrl, string thumbPreSignedUrl)
{
return new AttendanceLogVM
{
Id = attendanceLog.Id,
Comment = attendanceLog.Comment,
Employee = attendanceLog.Employee.ToBasicEmployeeVMFromEmployee(),
ActivityTime = attendanceLog.ActivityTime,
Activity = attendanceLog.Activity,
Photo = attendanceLog.Photo,
Latitude = attendanceLog.Latitude,
Longitude = attendanceLog.Longitude,
UpdatedOn = attendanceLog.UpdatedOn,
UpdatedByEmployee = attendanceLog.UpdatedByEmployee.ToBasicEmployeeVMFromEmployee(),
DocumentId = attendanceLog.Document.Id,
PreSignedUrl = preSignedUrl,
ThumbPreSignedUrl = thumbPreSignedUrl,
};
}
}
}

View File

@ -1,6 +1,4 @@
using System;
namespace Marco.Pms.Model.ViewModels.Activities
namespace Marco.Pms.Model.ViewModels.Activities
{
public class BasicEmployeeVM
{

View File

@ -0,0 +1,23 @@
using Marco.Pms.Model.Dtos.Attendance;
using Marco.Pms.Model.ViewModels.Activities;
namespace Marco.Pms.Model.ViewModels.AttendanceVM
{
public class AttendanceLogVM
{
public int Id { get; set; }
public string Comment { get; set; } = string.Empty;
public BasicEmployeeVM? Employee { get; set; }
public DateTime? ActivityTime { get; set; }
public ATTENDANCE_MARK_TYPE Activity { get; set; }
public byte[]? Photo { get; set; } // To store the captured photo
public string? Latitude { get; set; }
public string? Longitude { get; set; }
public DateTime UpdatedOn { get; set; }
public BasicEmployeeVM? UpdatedByEmployee { get; set; }
public Guid DocumentId { get; set; }
public string? PreSignedUrl { get; set; }
public string? ThumbPreSignedUrl { get; set; }
}
}

View File

@ -1,6 +1,6 @@
using Marco.Pms.Model.Dtos.Attendance;
namespace Marco.Pms.Model.ViewModels.Attendance
namespace Marco.Pms.Model.ViewModels.AttendanceVM
{
public class EmployeeAttendanceVM
{
@ -14,6 +14,7 @@ namespace Marco.Pms.Model.ViewModels.Attendance
public string? JobRoleName { get; set; }
public ATTENDANCE_MARK_TYPE Activity { get; set; }
public Guid? DocumentId { get; set; }
public string? ThumbPreSignedUrl { get; set; }
public string? PreSignedUrl { get; set; }

View File

@ -3,9 +3,10 @@ using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.AttendanceModule;
using Marco.Pms.Model.Dtos.Attendance;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Mapper;
using Marco.Pms.Model.Projects;
using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Attendance;
using Marco.Pms.Model.ViewModels.AttendanceVM;
using Marco.Pms.Services.Service;
using MarcoBMS.Services.Helpers;
using Microsoft.AspNetCore.Authorization;
@ -51,9 +52,15 @@ namespace MarcoBMS.Services.Controllers
{
int TenantId = GetTenantId();
List<AttendanceLog> lstAttendance = await _context.AttendanceLogs.Where(c => c.AttendanceId == attendanceid && c.TenantId == TenantId).ToListAsync();
return Ok(ApiResponse<object>.SuccessResponse(lstAttendance, System.String.Format("{0} Attendance records fetched successfully", lstAttendance.Count), 200));
List<AttendanceLog> lstAttendance = await _context.AttendanceLogs.Include(a => a.Document).Include(a => a.Employee).Include(a => a.UpdatedByEmployee).Where(c => c.AttendanceId == attendanceid && c.TenantId == TenantId).ToListAsync();
List<AttendanceLogVM> attendanceLogVMs = new List<AttendanceLogVM>();
foreach (var attendanceLog in lstAttendance)
{
string objectKey = attendanceLog.Document.S3Key;
string preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(objectKey);
attendanceLogVMs.Add(attendanceLog.ToAttendanceLogVMFromAttendanceLog(preSignedUrl, preSignedUrl));
}
return Ok(ApiResponse<object>.SuccessResponse(attendanceLogVMs, System.String.Format("{0} Attendance records fetched successfully", lstAttendance.Count), 200));
}
[HttpGet("log/employee/{employeeid}")]
@ -104,39 +111,38 @@ namespace MarcoBMS.Services.Controllers
}
var result = new List<EmployeeAttendanceVM>();
Attendance? attendance = null;
//Attendance? attendance = null;
ProjectAllocation? teamMember = null;
if (dateFrom == null) fromDate = DateTime.UtcNow.Date;
if (dateTo == null && dateFrom != null) toDate = fromDate.AddDays(-1);
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date <= fromDate && c.AttendanceDate.Date >= toDate && c.TenantId == TenantId).ToListAsync();
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date >= fromDate && c.AttendanceDate.Date <= toDate && c.TenantId == TenantId).ToListAsync();
List<ProjectAllocation> projectteam = await _projectsHelper.GetTeamByProject(TenantId, projectId, true);
foreach (ProjectAllocation teamMember in projectteam)
foreach (Attendance? attendance in lstAttendance)
{
var result1 = new EmployeeAttendanceVM()
{
EmployeeAvatar = null,
EmployeeId = teamMember.EmployeeId,
FirstName = teamMember.Employee.FirstName,
LastName = teamMember.Employee.LastName
Id = attendance.Id,
CheckInTime = attendance.InTime,
CheckOutTime = attendance.OutTime,
Activity = attendance.Activity
};
attendance = lstAttendance.Find(x => x.EmployeeID == teamMember.EmployeeId) ?? new Attendance();
if (attendance != null)
teamMember = projectteam.Find(x => x.EmployeeId == attendance.EmployeeID);
if (teamMember != null)
{
result1.Id = attendance.Id;
result1.CheckInTime = attendance.InTime;
result1.CheckOutTime = attendance.OutTime;
result1.Activity = attendance.Activity;
result1.EmployeeAvatar = null;
result1.EmployeeId = teamMember.EmployeeId;
result1.FirstName = teamMember.Employee.FirstName;
result1.LastName = teamMember.Employee.LastName;
result.Add(result1);
}
result.Add(result1);
}
return Ok(ApiResponse<object>.SuccessResponse(result, System.String.Format("{0} Attendance records fetched successfully", result.Count), 200));
}
@ -496,69 +502,99 @@ namespace MarcoBMS.Services.Controllers
_context.Attendes.Add(attendance);
}
byte[] fileBytes;
Document? document = null;
var Image = recordAttendanceDot.Image;
if (string.IsNullOrEmpty(Image.Base64Data))
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
try
{
//If base64 has a data URI prefix, strip it
var base64 = Image.Base64Data.Contains(",")
? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
: Image.Base64Data;
var objectKey = string.Empty;
var preSignedUrl = string.Empty;
fileBytes = Convert.FromBase64String(base64);
if (Image != null && Image.ContentType != null)
{
byte[] fileBytes;
if (string.IsNullOrEmpty(Image.Base64Data))
return BadRequest(ApiResponse<object>.ErrorResponse("Base64 data is missing", "Base64 data is missing", 400));
try
{
//If base64 has a data URI prefix, strip it
var base64 = Image.Base64Data.Contains(",")
? Image.Base64Data.Substring(Image.Base64Data.IndexOf(",") + 1)
: Image.Base64Data;
fileBytes = Convert.FromBase64String(base64);
}
catch (Exception ex)
{
return BadRequest(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400)); ;
}
using var stream = new MemoryStream(fileBytes);
string fileName = _s3Service.GenerateFileName(Image.ContentType, TenantId, "Attendance");
objectKey = await _s3Service.UploadFileAsync(stream, fileName, Image.ContentType);
preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(objectKey);
document = new Document
{
FileName = Image.FileName ?? fileName,
ContentType = Image.ContentType,
S3Key = objectKey,
Base64Data = Image.Base64Data,
FileSize = Image.FileSize,
UploadedAt = recordAttendanceDot.Date,
TenantId = TenantId
};
_context.Documents.Add(document);
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
return BadRequest(ApiResponse<object>.ErrorResponse(ex.Message, ex, 400)); ;
}
using var stream = new MemoryStream(fileBytes);
var objectKey = await _s3Service.UploadFileAsync(stream, Image.FileName, Image.ContentType);
var preSignedUrl = await _s3Service.GeneratePreSignedUrlAsync(objectKey);
Document document = new Document
{
FileName = Image.FileName,
ContentType = Image.ContentType,
S3Key = objectKey,
Base64Data = Image.Base64Data,
FileSize = Image.FileSize,
UploadedAt = recordAttendanceDot.Date,
TenantId = TenantId
};
_context.Documents.Add(document);
await _context.SaveChangesAsync();
// Step 3: Always insert a new log entry
var attendanceLog = new AttendanceLog
if (document != null)
{
AttendanceId = attendance.Id, // Use existing or new AttendanceId
Activity = attendance.Activity,
var attendanceLog = new AttendanceLog
{
AttendanceId = attendance.Id, // Use existing or new AttendanceId
Activity = attendance.Activity,
ActivityTime = finalDateTime,
Comment = recordAttendanceDot.Comment,
EmployeeID = recordAttendanceDot.EmployeeID,
Latitude = recordAttendanceDot.Latitude,
Longitude = recordAttendanceDot.Longitude,
DocumentId = document.Id,
TenantId = TenantId,
UpdatedBy = recordAttendanceDot.EmployeeID,
UpdatedOn = recordAttendanceDot.Date
};
_context.AttendanceLogs.Add(attendanceLog);
}
else
{
var attendanceLog = new AttendanceLog
{
AttendanceId = attendance.Id, // Use existing or new AttendanceId
Activity = attendance.Activity,
ActivityTime = finalDateTime,
Comment = recordAttendanceDot.Comment,
EmployeeID = recordAttendanceDot.EmployeeID,
Latitude = recordAttendanceDot.Latitude,
Longitude = recordAttendanceDot.Longitude,
DocumentId = document.Id,
TenantId = TenantId,
UpdatedBy = recordAttendanceDot.EmployeeID,
UpdatedOn = recordAttendanceDot.Date
};
_context.AttendanceLogs.Add(attendanceLog);
}
ActivityTime = finalDateTime,
Comment = recordAttendanceDot.Comment,
EmployeeID = recordAttendanceDot.EmployeeID,
Latitude = recordAttendanceDot.Latitude,
Longitude = recordAttendanceDot.Longitude,
DocumentId = document.Id,
TenantId = TenantId,
UpdatedBy = recordAttendanceDot.EmployeeID,
UpdatedOn = recordAttendanceDot.Date
};
//if (recordAttendanceDot.Image != null && recordAttendanceDot.Image.Count > 0)
//{
// attendanceLog.Photo = recordAttendanceDot.Image[0].Base64Data;
//}
_context.AttendanceLogs.Add(attendanceLog);
await _context.SaveChangesAsync();
await transaction.CommitAsync(); // Commit transaction
@ -566,20 +602,43 @@ namespace MarcoBMS.Services.Controllers
Employee employee = await _employeeHelper.GetEmployeeByID(recordAttendanceDot.EmployeeID);
if (employee.JobRole != null)
{
EmployeeAttendanceVM vm = new EmployeeAttendanceVM()
EmployeeAttendanceVM vm = new EmployeeAttendanceVM();
if (document != null)
{
CheckInTime = attendance.InTime,
CheckOutTime = attendance.OutTime,
EmployeeAvatar = null,
EmployeeId = recordAttendanceDot.EmployeeID,
FirstName = employee.FirstName,
LastName = employee.LastName,
Id = attendance.Id,
Activity = attendance.Activity,
JobRoleName = employee.JobRole.Name,
ThumbPreSignedUrl = preSignedUrl,
PreSignedUrl = preSignedUrl
};
vm = new EmployeeAttendanceVM()
{
CheckInTime = attendance.InTime,
CheckOutTime = attendance.OutTime,
EmployeeAvatar = null,
EmployeeId = recordAttendanceDot.EmployeeID,
FirstName = employee.FirstName,
LastName = employee.LastName,
Id = attendance.Id,
Activity = attendance.Activity,
JobRoleName = employee.JobRole.Name,
DocumentId = document.Id,
ThumbPreSignedUrl = preSignedUrl ?? string.Empty,
PreSignedUrl = preSignedUrl ?? string.Empty
};
}
else
{
vm = new EmployeeAttendanceVM()
{
CheckInTime = attendance.InTime,
CheckOutTime = attendance.OutTime,
EmployeeAvatar = null,
EmployeeId = recordAttendanceDot.EmployeeID,
FirstName = employee.FirstName,
LastName = employee.LastName,
Id = attendance.Id,
Activity = attendance.Activity,
JobRoleName = employee.JobRole.Name,
DocumentId = Guid.Empty,
ThumbPreSignedUrl = string.Empty,
PreSignedUrl = string.Empty
};
}
return Ok(ApiResponse<object>.SuccessResponse(vm, "Attendance marked successfully.", 200));
}

View File

@ -43,8 +43,4 @@
<ProjectReference Include="..\Marco.Pms.Utility\Marco.Pms.Utility.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="images\" />
</ItemGroup>
</Project>

View File

@ -16,13 +16,23 @@ using Serilog;
var builder = WebApplication.CreateBuilder(args);
// Add Serilog Configuration
string? mongoConn = builder.Configuration["MongoDB:SerilogDatabaseUrl"];
string timeString = "00:00:30";
TimeSpan.TryParse(timeString, out TimeSpan timeSpan);
// Add Serilog Configuration
builder.Host.UseSerilog((context, config) =>
{
config.ReadFrom.Configuration(context.Configuration); // Taking all configuration from appsetting.json
config.ReadFrom.Configuration(context.Configuration) // Taking all configuration from appsetting.json
.WriteTo.MongoDB(
databaseUrl: mongoConn,
collectionName: "api-logs",
batchPostingLimit: 100,
period: timeSpan
);
})
;
});
// Add services
var corsSettings = builder.Configuration.GetSection("Cors");

View File

@ -2,6 +2,7 @@
using Amazon.S3.Model;
using Amazon.S3.Transfer;
using Marco.Pms.Model.Utilities;
using MarcoBMS.Services.Service;
using Microsoft.Extensions.Options;
namespace Marco.Pms.Services.Service
@ -11,9 +12,11 @@ namespace Marco.Pms.Services.Service
{
private readonly IAmazonS3 _s3Client;
private readonly string _bucketName = "your-bucket-name";
private readonly ILoggingService _logger;
public S3UploadService(IOptions<AWSSettings> awsOptions)
public S3UploadService(IOptions<AWSSettings> awsOptions, ILoggingService logger)
{
_logger = logger;
var settings = awsOptions.Value;
var region = Amazon.RegionEndpoint.GetBySystemName(settings.Region);
@ -25,7 +28,7 @@ namespace Marco.Pms.Services.Service
public async Task<string> UploadFileAsync(Stream fileStream, string fileName, string contentType)
{
// Generate a unique object key (you can customize this)
var objectKey = $"{Guid.NewGuid()}_{fileName}";
var objectKey = $"{fileName}";
var uploadRequest = new TransferUtilityUploadRequest
{
@ -38,7 +41,7 @@ namespace Marco.Pms.Services.Service
var transferUtility = new TransferUtility(_s3Client);
await transferUtility.UploadAsync(uploadRequest);
_logger.LogInfo("File uploaded to Amazon S3");
return objectKey;
}
public async Task<string> GeneratePreSignedUrlAsync(string objectKey)
@ -53,7 +56,16 @@ namespace Marco.Pms.Services.Service
};
string url = _s3Client.GetPreSignedURL(request);
_logger.LogInfo("Requested presigned url from Amazon S3");
return url;
}
public string GenerateFileName(string contentType, int tenantId, string? name)
{
string extenstion = contentType.Split("/")[1];
if (string.IsNullOrEmpty(name))
return $"{tenantId}_{DateTime.UtcNow:yyyyMMddHHmmssfff}.{extenstion}";
return $"{name}_{tenantId}_{DateTime.UtcNow:yyyyMMddHHmmssfff}.{extenstion}";
}
}
}