Ashutosh_Task#513 #96

Merged
ashutosh.nehete merged 58 commits from Ashutosh_Task#513 into Issues_June_3W 2025-06-28 04:27:28 +00:00
8 changed files with 3557 additions and 20 deletions
Showing only changes of commit cb2856b2df - Show all commits

View File

@ -0,0 +1,101 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Marco.Pms.DataAccess.Migrations
{
/// <inheritdoc />
public partial class Added_UpdatedBy_In_Contacts_And_ContactNotes_Table : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedAt",
table: "Contacts",
type: "datetime(6)",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "UpdatedById",
table: "Contacts",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci");
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedAt",
table: "ContactNotes",
type: "datetime(6)",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "UpdatedById",
table: "ContactNotes",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci");
migrationBuilder.CreateIndex(
name: "IX_Contacts_UpdatedById",
table: "Contacts",
column: "UpdatedById");
migrationBuilder.CreateIndex(
name: "IX_ContactNotes_UpdatedById",
table: "ContactNotes",
column: "UpdatedById");
migrationBuilder.AddForeignKey(
name: "FK_ContactNotes_Employees_UpdatedById",
table: "ContactNotes",
column: "UpdatedById",
principalTable: "Employees",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Contacts_Employees_UpdatedById",
table: "Contacts",
column: "UpdatedById",
principalTable: "Employees",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_ContactNotes_Employees_UpdatedById",
table: "ContactNotes");
migrationBuilder.DropForeignKey(
name: "FK_Contacts_Employees_UpdatedById",
table: "Contacts");
migrationBuilder.DropIndex(
name: "IX_Contacts_UpdatedById",
table: "Contacts");
migrationBuilder.DropIndex(
name: "IX_ContactNotes_UpdatedById",
table: "ContactNotes");
migrationBuilder.DropColumn(
name: "UpdatedAt",
table: "Contacts");
migrationBuilder.DropColumn(
name: "UpdatedById",
table: "Contacts");
migrationBuilder.DropColumn(
name: "UpdatedAt",
table: "ContactNotes");
migrationBuilder.DropColumn(
name: "UpdatedById",
table: "ContactNotes");
}
}
}

View File

@ -393,6 +393,12 @@ namespace Marco.Pms.DataAccess.Migrations
b.Property<Guid>("TenantId")
.HasColumnType("char(36)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<Guid?>("UpdatedById")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("ContactCategoryId");
@ -401,6 +407,8 @@ namespace Marco.Pms.DataAccess.Migrations
b.HasIndex("TenantId");
b.HasIndex("UpdatedById");
b.ToTable("Contacts");
});
@ -504,6 +512,12 @@ namespace Marco.Pms.DataAccess.Migrations
b.Property<Guid>("TenantId")
.HasColumnType("char(36)");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<Guid?>("UpdatedById")
.HasColumnType("char(36)");
b.HasKey("Id");
b.HasIndex("ContactId");
@ -512,6 +526,8 @@ namespace Marco.Pms.DataAccess.Migrations
b.HasIndex("TenantId");
b.HasIndex("UpdatedById");
b.ToTable("ContactNotes");
});
@ -2618,11 +2634,17 @@ namespace Marco.Pms.DataAccess.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Marco.Pms.Model.Employees.Employee", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
b.Navigation("ContactCategory");
b.Navigation("CreatedBy");
b.Navigation("Tenant");
b.Navigation("UpdatedBy");
});
modelBuilder.Entity("Marco.Pms.Model.Directory.ContactBucketMapping", b =>
@ -2686,11 +2708,17 @@ namespace Marco.Pms.DataAccess.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Marco.Pms.Model.Employees.Employee", "UpdatedBy")
.WithMany()
.HasForeignKey("UpdatedById");
b.Navigation("Contact");
b.Navigation("Createdby");
b.Navigation("Tenant");
b.Navigation("UpdatedBy");
});
modelBuilder.Entity("Marco.Pms.Model.Directory.ContactPhone", b =>

View File

@ -20,6 +20,11 @@ namespace Marco.Pms.Model.Directory
[ValidateNever]
[ForeignKey("CreatedById")]
public Employee? CreatedBy { get; set; }
public Guid? UpdatedById { get; set; }
[ValidateNever]
[ForeignKey("UpdatedById")]
public Employee? UpdatedBy { get; set; }
[DisplayName("ContactCategoryId")]
public Guid? ContactCategoryId { get; set; }
@ -27,5 +32,6 @@ namespace Marco.Pms.Model.Directory
[ForeignKey(nameof(ContactCategoryId))]
public ContactCategoryMaster? ContactCategory { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
}
}

View File

@ -14,7 +14,13 @@ namespace Marco.Pms.Model.Directory
[ValidateNever]
[ForeignKey("CreatedById")]
public Employee? Createdby { get; set; }
public Guid? UpdatedById { get; set; }
[ValidateNever]
[ForeignKey("UpdatedById")]
public Employee? UpdatedBy { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
public Guid ContactId { get; set; }
[ValidateNever]

View File

@ -234,7 +234,9 @@ namespace Marco.Pms.Model.Mapper
Note = note.Note,
ContactId = note.ContactId,
CreatedAt = note.CreatedAt,
UpdatedAt = note.UpdatedAt,
CreatedBy = note.Createdby != null ? note.Createdby.ToBasicEmployeeVMFromEmployee() : null,
UpdatedBy = note.UpdatedBy != null ? note.UpdatedBy.ToBasicEmployeeVMFromEmployee() : null,
IsActive = note.IsActive
};
}

View File

@ -158,6 +158,13 @@ namespace Marco.Pms.Services.Controllers
// -------------------------------- Contact Notes --------------------------------
[HttpGet("notes")]
public async Task<IActionResult> GetListOFAllNotes([FromQuery] int? pageSize, [FromQuery] int pageNumber)
{
var response = await _directoryHelper.GetListOFAllNotes(pageSize ?? 25, pageNumber);
return StatusCode(response.StatusCode, response);
}
[HttpPost("note")]
public async Task<IActionResult> CreateContactNote([FromBody] CreateContactNoteDto noteDto)
{

View File

@ -7,6 +7,7 @@ using Marco.Pms.Model.Utilities;
using Marco.Pms.Model.ViewModels.Directory;
using Marco.Pms.Model.ViewModels.Master;
using Marco.Pms.Model.ViewModels.Projects;
using Marco.Pms.Services.Service;
using MarcoBMS.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.EntityFrameworkCore;
@ -18,15 +19,17 @@ namespace Marco.Pms.Services.Helpers
private readonly ApplicationDbContext _context;
private readonly ILoggingService _logger;
private readonly UserHelper _userHelper;
private readonly PermissionServices _permissionServices;
private readonly Guid directoryAdmin;
private readonly Guid directoryManager;
private readonly Guid directoryUser;
public DirectoryHelper(ApplicationDbContext context, ILoggingService logger, UserHelper userHelper)
public DirectoryHelper(ApplicationDbContext context, ILoggingService logger, UserHelper userHelper, PermissionServices permissionServices)
{
_context = context;
_logger = logger;
_userHelper = userHelper;
_permissionServices = permissionServices;
directoryAdmin = Guid.Parse("4286a13b-bb40-4879-8c6d-18e9e393beda");
directoryManager = Guid.Parse("62668630-13ce-4f52-a0f0-db38af2230c5");
directoryUser = Guid.Parse("0f919170-92d4-4337-abd3-49b66fc871bb");
@ -504,6 +507,8 @@ namespace Marco.Pms.Services.Helpers
var newContact = updateContact.ToContactFromUpdateContactDto(tenantId, contact);
newContact.UpdatedById = LoggedInEmployee.Id;
newContact.UpdatedAt = DateTime.UtcNow;
_context.Contacts.Update(newContact);
await _context.SaveChangesAsync();
@ -893,6 +898,110 @@ namespace Marco.Pms.Services.Helpers
// -------------------------------- Contact Notes --------------------------------
/// <summary>
/// Retrieves a paginated list of contact notes based on user permissions.
/// </summary>
/// <param name="pageSize">The number of items per page.</param>
/// <param name="pageNumber">The current page number.</param>
/// <returns>An ApiResponse containing the paginated notes or an error message.</returns>
public async Task<ApiResponse<object>> GetListOFAllNotes(int pageSize, int pageNumber)
{
_logger.LogInfo("Attempting to fetch list of all notes. PageSize: {PageSize}, PageNumber: {PageNumber}", pageSize, pageNumber);
Guid tenantId = _userHelper.GetTenantId();
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
if (loggedInEmployee == null)
{
_logger.LogWarning("GetListOFAllNotes: LoggedInEmployee is null. Cannot proceed.");
return ApiResponse<object>.ErrorResponse("Unauthorized", "Employee not found.", 401);
}
// --- Permission Checks ---
var hasAdminPermission = await _permissionServices.HasPermission(directoryAdmin, loggedInEmployee.Id);
var hasManagerPermission = await _permissionServices.HasPermission(directoryManager, loggedInEmployee.Id);
var hasUserPermission = await _permissionServices.HasPermission(directoryUser, loggedInEmployee.Id);
IQueryable<ContactNote> notesQuery = _context.ContactNotes
.Include(cn => cn.UpdatedBy)
.Include(cn => cn.Createdby) // Assuming 'CreatedBy' (PascalCase)
.Where(cn => cn.TenantId == tenantId)
.AsQueryable(); // Start building the query
if (!hasAdminPermission && !(hasManagerPermission || hasUserPermission))
{
_logger.LogWarning("GetListOFAllNotes: User {EmployeeId} does not have required permissions to access notes for TenantId: {TenantId}", loggedInEmployee.Id, tenantId);
return ApiResponse<object>.ErrorResponse("Access Denied", "You don't have access to view notes.", 403);
}
if (!hasAdminPermission) // If not an admin, apply additional filtering
{
_logger.LogInfo("GetListOFAllNotes: User {EmployeeId} is not an admin. Applying manager/user specific filters.", loggedInEmployee.Id);
var assignedBucketIds = await _context.EmployeeBucketMappings
.Where(eb => eb.EmployeeId == loggedInEmployee.Id)
.Select(eb => eb.BucketId)
.ToListAsync();
if (!assignedBucketIds.Any())
{
_logger.LogInfo("GetListOFAllNotes: User {EmployeeId} has no assigned buckets. Returning empty list.", loggedInEmployee.Id);
return ApiResponse<object>.SuccessResponse(new { CurrentPage = pageNumber, TotalPages = 0, Data = new List<ContactNoteVM>() }, "No notes found based on assigned buckets.", 200);
}
var contactIds = await _context.ContactBucketMappings
.Where(cb => assignedBucketIds.Contains(cb.BucketId))
.Select(cb => cb.ContactId)
.ToListAsync();
if (!contactIds.Any())
{
_logger.LogInfo("GetListOFAllNotes: No contacts found for assigned buckets for user {EmployeeId}. Returning empty list.", loggedInEmployee.Id);
return ApiResponse<object>.SuccessResponse(new { CurrentPage = pageNumber, TotalPages = 0, Data = new List<ContactNoteVM>() }, "No notes found for associated contacts.", 200);
}
notesQuery = notesQuery.Where(cn => contactIds.Contains(cn.ContactId));
}
// --- Pagination Logic ---
// Ensure pageSize and pageNumber are valid
pageSize = pageSize < 1 ? 25 : pageSize; // Default to 25 if less than 1
pageNumber = pageNumber < 1 ? 1 : pageNumber; // Default to 1 if less than 1
// Get total count BEFORE applying Skip/Take for accurate pagination metadata
int totalRecords = await notesQuery.CountAsync();
int totalPages = (int)Math.Ceiling((double)totalRecords / pageSize);
int skip = (pageNumber - 1) * pageSize;
// --- Apply Ordering and Pagination in the database ---
List<ContactNote> notes = await notesQuery
.OrderByDescending(cn => (cn.UpdatedAt != null ? cn.UpdatedAt : cn.CreatedAt)) // Order by updated date or created date
.Skip(skip)
.Take(pageSize)
.ToListAsync();
_logger.LogInfo("GetListOFAllNotes: Fetched {Count} notes for page {PageNumber} of {TotalPages} total pages. Total records: {TotalRecords}.",
notes.Count, pageNumber, totalPages, totalRecords);
// --- Map to ViewModel (in-memory) ---
// This mapping is done in memory because ToBasicEmployeeVMFromEmployee() is likely a C# method
// that cannot be translated to SQL by Entity Framework.
List<ContactNoteVM> noteVMS = notes
.Select(cn => cn.ToContactNoteVMFromContactNote())
.ToList();
var response = new
{
CurrentPage = pageNumber,
PageSize = pageSize, // Include pageSize in response for client clarity
TotalPages = totalPages,
TotalRecords = totalRecords, // Add total records for client
Data = noteVMS
};
_logger.LogInfo("GetListOFAllNotes: Successfully retrieved notes and mapped to ViewModel for TenantId: {TenantId}.", tenantId);
return ApiResponse<object>.SuccessResponse(response, $"{noteVMS.Count} notes fetched successfully.", 200);
}
public async Task<ApiResponse<object>> GetNoteListByContactId(Guid id, bool active)
{
Guid tenantId = _userHelper.GetTenantId();
@ -903,32 +1012,25 @@ namespace Marco.Pms.Services.Helpers
List<ContactNote> notes = new List<ContactNote>();
if (active)
{
notes = await _context.ContactNotes.Where(n => n.ContactId == contact.Id && n.IsActive && n.TenantId == tenantId).ToListAsync();
notes = await _context.ContactNotes
.Include(n => n.Createdby)
.Include(n => n.UpdatedBy)
.Where(n => n.ContactId == contact.Id && n.IsActive && n.TenantId == tenantId)
.ToListAsync();
}
else
{
notes = await _context.ContactNotes.Where(n => n.ContactId == contact.Id && n.TenantId == tenantId).ToListAsync();
notes = await _context.ContactNotes
.Include(n => n.Createdby)
.Include(n => n.UpdatedBy)
.Where(n => n.ContactId == contact.Id && n.TenantId == tenantId)
.ToListAsync();
}
var noteIds = notes.Select(n => n.Id).ToList();
List<DirectoryUpdateLog>? updateLogs = await _context.DirectoryUpdateLogs.Include(l => l.Employee).Where(l => noteIds.Contains(l.RefereanceId)).ToListAsync();
List<ContactNoteVM>? noteVMs = new List<ContactNoteVM>();
foreach (var note in notes)
{
ContactNoteVM noteVM = note.ToContactNoteVMFromContactNote();
DirectoryUpdateLog? updateLog = updateLogs.Where(l => l.RefereanceId == note.Id).OrderByDescending(l => l.UpdateAt).FirstOrDefault();
if (updateLog != null)
{
noteVM.UpdatedAt = updateLog.UpdateAt;
noteVM.UpdatedBy = updateLog.Employee != null ? updateLog.Employee.ToBasicEmployeeVMFromEmployee() : null;
}
else
{
noteVM.UpdatedAt = note.CreatedAt;
noteVM.UpdatedBy = note.Createdby != null ? note.Createdby.ToBasicEmployeeVMFromEmployee() : null;
}
noteVMs.Add(noteVM);
//List<ContactNoteVM>? noteVMs = new List<ContactNoteVM>();
List<ContactNoteVM>? noteVMs = notes.Select(n => n.ToContactNoteVMFromContactNote()).ToList();
}
_logger.LogInfo("{count} contact-notes record from contact {ContactId} fetched by Employee {EmployeeId}", noteVMs.Count, id, LoggedInEmployee.Id);
return ApiResponse<object>.SuccessResponse(noteVMs, $"{noteVMs.Count} contact-notes record fetched successfully", 200);
}
@ -970,6 +1072,8 @@ namespace Marco.Pms.Services.Helpers
if (contactNote != null)
{
contactNote.Note = noteDto.Note;
contactNote.UpdatedById = LoggedInEmployee.Id;
contactNote.UpdatedAt = DateTime.UtcNow;
_context.DirectoryUpdateLogs.Add(new DirectoryUpdateLog
{
@ -1335,6 +1439,9 @@ namespace Marco.Pms.Services.Helpers
_logger.LogWarning("Employee {EmployeeId} tries to delete bucket {BucketId} but not found in database", LoggedInEmployee.Id, id);
return ApiResponse<object>.SuccessResponse(new { }, "Bucket deleted successfully", 200);
}
// -------------------------------- Helper --------------------------------
private bool Compare(string sentence, string search)
{
sentence = sentence.Trim().ToLower();