using Azure.Core; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Mono.TextTemplating; using Newtonsoft.Json; using PSTW_CentralSystem.Areas.OTcalculate.Models; using PSTW_CentralSystem.Controllers.API; using PSTW_CentralSystem.Controllers.API.Inventory; using PSTW_CentralSystem.DBContext; using PSTW_CentralSystem.Models; using System.ComponentModel.Design; using System.Data; using System; using System.Threading.Tasks; using System.Diagnostics; using System.Reflection; using static System.Collections.Specialized.BitVector32; using System.Security.Claims; using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages; using Microsoft.AspNetCore.Mvc.Rendering; using QuestPDF.Fluent; using QuestPDF.Helpers; using QuestPDF.Infrastructure; using PSTW_CentralSystem.Areas.OTcalculate.Services; using Microsoft.AspNetCore.Hosting; using DocumentFormat.OpenXml.InkML; using static PSTW_CentralSystem.Areas.OTcalculate.Models.OtRegisterModel; namespace PSTW_CentralSystem.Controllers.API { [ApiController] [Route("[controller]")] public class OvertimeAPI : Controller { private readonly ILogger _logger; private readonly CentralSystemContext _centralDbContext; private readonly UserManager _userManager; private readonly IWebHostEnvironment _env; public OvertimeAPI(ILogger logger, CentralSystemContext centralDbContext, UserManager userManager, IWebHostEnvironment env) { _logger = logger; _centralDbContext = centralDbContext; _userManager = userManager; _env = env; } #region Settings [HttpGet("GetUpdateDates")] public IActionResult GetUpdateDates() { try { var latestRateUpdate = _centralDbContext.Rates.OrderByDescending(r => r.LastUpdated).FirstOrDefault()?.LastUpdated; var latestCalendarUpdate = _centralDbContext.Holidays.OrderByDescending(c => c.LastUpdated).FirstOrDefault()?.LastUpdated; var latestFlexiHourUpdate = _centralDbContext.Hrusersetting.OrderByDescending(r => r.FlexiHourUpdate).FirstOrDefault()?.FlexiHourUpdate; var latestRegionUpdate = _centralDbContext.Hrusersetting.OrderByDescending(c => c.StateUpdate).FirstOrDefault()?.StateUpdate; var latestApprovalFlowUpdate = _centralDbContext.Hrusersetting.OrderByDescending(c => c.ApprovalUpdate).FirstOrDefault()?.ApprovalUpdate; var updateDates = new { rateUpdateDate = latestRateUpdate.HasValue ? latestRateUpdate.Value.ToString("dd MMMM yyyy") : null, calendarUpdateDate = latestCalendarUpdate.HasValue ? latestCalendarUpdate.Value.ToString("dd MMMM yyyy") : null, flexiHourUpdateDate = latestFlexiHourUpdate.HasValue ? latestFlexiHourUpdate.Value.ToString("dd MMMM yyyy") : null, regionUpdateDate = latestRegionUpdate.HasValue ? latestRegionUpdate.Value.ToString("dd MMMM yyyy") : null, approvalFlowUpdateDate = latestApprovalFlowUpdate.HasValue ? latestApprovalFlowUpdate.Value.ToString("dd MMMM yyyy") : null }; return Json(updateDates); } catch (Exception ex) { return BadRequest(ex.Message); } } [HttpGet("CheckIncompleteUserSettings")] public async Task CheckIncompleteUserSettings() { try { var incompleteUserIds = new List(); var allUserIds = await _userManager.Users.Select(u => u.Id).ToListAsync(); foreach (var userId in allUserIds) { bool isIncomplete = false; var hrUserSetting = await _centralDbContext.Hrusersetting .Where(h => h.UserId == userId) .FirstOrDefaultAsync(); if (hrUserSetting == null) { isIncomplete = true; } else { if (hrUserSetting.FlexiHourId == null || hrUserSetting.FlexiHourId == 0) { isIncomplete = true; } if (hrUserSetting.StateId == null || hrUserSetting.StateId == 0) { isIncomplete = true; } if (hrUserSetting.ApprovalFlowId == null || hrUserSetting.ApprovalFlowId == 0) { isIncomplete = true; } } var rateSetting = await _centralDbContext.Rates .Where(r => r.UserId == userId) .FirstOrDefaultAsync(); if (rateSetting == null) { isIncomplete = true; } else { if (rateSetting.RateValue == 0.00m) { isIncomplete = true; } } if (isIncomplete) { incompleteUserIds.Add(userId); } } if (incompleteUserIds.Any()) { return Ok(new { hasIncompleteSettings = true, numberOfIncompleteUsers = incompleteUserIds.Count }); } else { return Ok(new { hasIncompleteSettings = false, numberOfIncompleteUsers = 0 }); } } catch (Exception ex) { return StatusCode(500, $"An error occurred: {ex.Message}"); } } #endregion #region Rate [HttpPost("UpdateRates")] public async Task UpdateRate([FromBody] List rates) { if (!ModelState.IsValid) { return BadRequest(ModelState); } try { foreach (var rate in rates) { var existingRate = await _centralDbContext.Rates .FirstOrDefaultAsync(r => r.UserId == rate.UserId); if (existingRate != null) { existingRate.RateValue = rate.RateValue; existingRate.LastUpdated = DateTime.Now; _centralDbContext.Rates.Update(existingRate); } else { _centralDbContext.Rates.Add(new RateModel { UserId = rate.UserId, RateValue = rate.RateValue, LastUpdated = DateTime.Now }); } } await _centralDbContext.SaveChangesAsync(); var updatedRates = await _centralDbContext.Rates .Include(r => r.Users) .Select(r => new { r.RateId, r.RateValue, r.UserId, FullName = r.Users.FullName, DepartmentName = r.Users.Department.DepartmentName }) .ToListAsync(); return Json(updatedRates); } catch (Exception ex) { return BadRequest(ex.Message); } } [HttpPost("GetUserRates")] public async Task GetUserRates() { try { var userRates = await _centralDbContext.Rates .Include(rates => rates.Users) .ThenInclude(user => user.Department) .Select(rates => new { rates.RateId, rates.RateValue, rates.UserId, rates.Users.FullName, rates.Users.Department.DepartmentName }) .ToListAsync(); return Json(userRates); } catch (Exception ex) { return BadRequest(ex.Message); } } #endregion #region FlexiHour State Approval private async Task UpdateOrInsertUserSettingAsync(int userId, int? flexiHourId = null, int? stateId = null, int? approvalFlowId = null) { var setting = await _centralDbContext.Hrusersetting .FirstOrDefaultAsync(h => h.UserId == userId); if (setting != null) { if (flexiHourId.HasValue) { setting.FlexiHourId = flexiHourId; setting.FlexiHourUpdate = DateTime.Now; } if (stateId.HasValue) { setting.StateId = stateId; setting.StateUpdate = DateTime.Now; } if (approvalFlowId.HasValue) { setting.ApprovalFlowId = approvalFlowId; setting.ApprovalUpdate = DateTime.Now; } _centralDbContext.Hrusersetting.Update(setting); } else { var newSetting = new HrUserSettingModel { UserId = userId, FlexiHourId = flexiHourId, FlexiHourUpdate = flexiHourId.HasValue ? DateTime.Now : null, StateId = stateId, StateUpdate = stateId.HasValue ? DateTime.Now : null, ApprovalFlowId = approvalFlowId, ApprovalUpdate = approvalFlowId.HasValue ? DateTime.Now : null }; _centralDbContext.Hrusersetting.Add(newSetting); } } #endregion #region FlexiHour [HttpGet("GetFlexiHours")] public IActionResult GetAllFlexiHours() { var flexiHours = _centralDbContext.Flexihour .Select(f => new { f.FlexiHourId, f.FlexiHour }) .ToList(); return Ok(flexiHours); } [HttpGet("GetUserFlexiHours")] public async Task GetUserFlexiHours() { try { var users = await _centralDbContext.Users .Include(u => u.Department) .ToListAsync(); var hrUserSettings = await _centralDbContext.Hrusersetting .Include(hr => hr.FlexiHour) .ToListAsync(); var result = users.Select(u => new { UserId = u.Id, FullName = u.FullName, DepartmentName = u.Department != null ? u.Department.DepartmentName : "N/A", FlexiHour = hrUserSettings .Where(hr => hr.UserId == u.Id) .Select(hr => hr.FlexiHour != null ? hr.FlexiHour.FlexiHour : "N/A") .FirstOrDefault() ?? "N/A", State = hrUserSettings .Where(hr => hr.UserId == u.Id) .Select(hr => hr.State != null ? hr.State.StateName : "N/A") .FirstOrDefault() ?? "N/A" }).ToList(); Console.WriteLine(JsonConvert.SerializeObject(result)); return Ok(result); } catch (Exception ex) { return BadRequest(new { message = ex.Message }); } } [HttpPost("UpdateUserFlexiHours")] public async Task UpdateUserFlexiHours([FromBody] List updates) { if (!ModelState.IsValid) return BadRequest(ModelState); try { foreach (var update in updates) { await UpdateOrInsertUserSettingAsync(update.UserId, flexiHourId: update.FlexiHourId); } await _centralDbContext.SaveChangesAsync(); return Ok(); } catch (Exception ex) { return BadRequest(new { message = ex.Message }); } } #endregion #region State [HttpGet("GetUserStates")] public async Task GetUserStates() { try { var users = await _centralDbContext.Users .Include(u => u.Department) .ToListAsync(); var hrUserSettings = await _centralDbContext.Hrusersetting .Include(h => h.State) .Include(h => h.Approvalflow) .ToListAsync(); var result = users.Select(u => { var hrSetting = hrUserSettings.FirstOrDefault(h => h.UserId == u.Id); return new { u.Id, u.FullName, DepartmentName = u.Department != null ? u.Department.DepartmentName : "N/A", State = hrSetting != null && hrSetting.State != null ? hrSetting.State.StateName : "N/A", approvalName = hrSetting?.Approvalflow?.ApprovalName ?? "N/A" }; }).ToList(); return Ok(result); } catch (Exception ex) { return StatusCode(500, ex.Message); } } [HttpPost("UpdateUserStates")] public async Task UpdateUserStates([FromBody] List updates) { if (!ModelState.IsValid) return BadRequest(ModelState); try { foreach (var update in updates) { var existingSetting = await _centralDbContext.Hrusersetting.FirstOrDefaultAsync(h => h.UserId == update.UserId); if (existingSetting != null) { existingSetting.StateId = update.StateId; existingSetting.StateUpdate = DateTime.Now; } else { _centralDbContext.Hrusersetting.Add(new HrUserSettingModel { UserId = update.UserId, StateId = update.StateId, StateUpdate = DateTime.Now, }); } } await _centralDbContext.SaveChangesAsync(); return Ok(); } catch (Exception ex) { return BadRequest(new { message = ex.Message }); } } [HttpPost("UpdateUserApprovalFlow")] public async Task UpdateUserApprovalFlow([FromBody] List updates) { if (!ModelState.IsValid) return BadRequest(ModelState); try { foreach (var update in updates) { var existingSetting = await _centralDbContext.Hrusersetting.FirstOrDefaultAsync(h => h.UserId == update.UserId); if (existingSetting != null) { existingSetting.ApprovalFlowId = update.ApprovalFlowId; existingSetting.ApprovalUpdate = DateTime.Now; } else { _centralDbContext.Hrusersetting.Add(new HrUserSettingModel { UserId = update.UserId, ApprovalFlowId = update.ApprovalFlowId, ApprovalUpdate = DateTime.Now }); } } await _centralDbContext.SaveChangesAsync(); return Ok(); } catch (Exception ex) { return BadRequest(new { message = ex.Message }); } } [HttpPost("UpdateApprovalFlow")] public async Task UpdateApprovalFlow([FromBody] ApprovalFlowModel model) { if (!ModelState.IsValid) return BadRequest("Invalid data"); var existing = await _centralDbContext.Approvalflow.FindAsync(model.ApprovalFlowId); if (existing == null) return NotFound("Approval flow not found"); existing.ApprovalName = model.ApprovalName; existing.HoU = model.HoU; existing.HoD = model.HoD; existing.Manager = model.Manager; existing.HR = model.HR; await _centralDbContext.SaveChangesAsync(); return Ok(); } [HttpGet("GetApprovalFlowList")] public async Task GetApprovalFlowList() { try { var flows = await _centralDbContext.Approvalflow .Select(af => new { af.ApprovalFlowId, af.ApprovalName, hou = af.HoU, hod = af.HoD, manager = af.Manager, hr = af.HR }) .ToListAsync(); return Ok(flows); } catch (Exception ex) { return StatusCode(500, new { message = ex.Message }); } } #endregion #region Approval Flow [HttpPost("CreateApprovalFlow")] public async Task CreateApprovalFlow([FromBody] ApprovalFlowModel model) { if (string.IsNullOrEmpty(model.ApprovalName)) return BadRequest(new { message = "Approval Name is required." }); if (!model.HR.HasValue) return BadRequest(new { message = "HR approver is required." }); if (!ModelState.IsValid) return BadRequest(new { message = "Invalid approval flow data." }); try { _centralDbContext.Approvalflow.Add(model); await _centralDbContext.SaveChangesAsync(); return Ok(new { message = "Approval flow created successfully." }); } catch (Exception ex) { return StatusCode(500, new { message = $"Error saving approval flow: {ex.InnerException?.Message ?? ex.Message}" }); } } [HttpGet("GetApprovalFlowRecord")] public async Task GetApprovalFlowRecord() { try { var approvalFlows = await _centralDbContext.Approvalflow .Select(af => new { af.ApprovalFlowId, af.ApprovalName }) .ToListAsync(); return Ok(approvalFlows); } catch (Exception ex) { return StatusCode(500, new { message = $"Error fetching approval flows: {ex.Message}" }); } } [HttpPost("EditApprovalFlow")] public async Task EditApprovalFlow([FromBody] ApprovalFlowModel model) { if (!ModelState.IsValid) { return BadRequest("Invalid data."); } var existingFlow = await _centralDbContext.Approvalflow.FindAsync(model.ApprovalFlowId); if (existingFlow == null) { return NotFound("Approval flow not found."); } existingFlow.ApprovalName = model.ApprovalName; existingFlow.HoU = model.HoU; existingFlow.HoD = model.HoD; existingFlow.Manager = model.Manager; existingFlow.HR = model.HR; try { await _centralDbContext.SaveChangesAsync(); return Ok(new { message = "Approval flow updated successfully." }); } catch (Exception ex) { return StatusCode(500, new { message = "Failed to update approval flow.", detail = ex.Message }); } } [HttpDelete("DeleteApprovalFlow/{id}")] public async Task DeleteApprovalFlow(int id) { try { var approvalFlow = await _centralDbContext.Approvalflow.FindAsync(id); if (approvalFlow == null) { return NotFound(new { message = "Approval flow not found." }); } var usersWithThisFlow = await _centralDbContext.Hrusersetting .AnyAsync(u => u.ApprovalFlowId == id); if (usersWithThisFlow) { return BadRequest(new { message = "Cannot delete this approval flow because it's being used by one or more users." }); } _centralDbContext.Approvalflow.Remove(approvalFlow); await _centralDbContext.SaveChangesAsync(); return Ok(new { message = "Approval flow deleted successfully." }); } catch (Exception ex) { return StatusCode(500, new { message = $"Error deleting approval flow: {ex.Message}" }); } } [HttpGet("GetAllUsers")] public async Task GetAllUsers() { try { var users = await _centralDbContext.Users .Select(u => new { u.Id, u.FullName }) .ToListAsync(); return Ok(users); } catch (Exception ex) { return StatusCode(500, new { message = ex.Message }); } } #endregion #region Calendar [HttpGet("GetStatesName")] public async Task GetStatesName() { try { var states = await _centralDbContext.States .Select(s => new { s.StateId, s.StateName }) .ToListAsync(); return Json(states); } catch (Exception ex) { return BadRequest(ex.Message); } } [HttpPost("UpdateHoliday")] public async Task UpdateHoliday([FromBody] List holidays) { if (!ModelState.IsValid) { return BadRequest(ModelState); } try { foreach (var calendar in holidays) { var existingCalendar = await _centralDbContext.Holidays .FirstOrDefaultAsync(h => h.StateId == calendar.StateId && h.HolidayDate == calendar.HolidayDate); if (existingCalendar != null) { existingCalendar.HolidayName = calendar.HolidayName; existingCalendar.LastUpdated = DateTime.Now; _centralDbContext.Holidays.Update(existingCalendar); } else { _centralDbContext.Holidays.Add(new CalendarModel { HolidayName = calendar.HolidayName, HolidayDate = calendar.HolidayDate, StateId = calendar.StateId, LastUpdated = DateTime.Now }); } } await _centralDbContext.SaveChangesAsync(); var updatedHoliday = await _centralDbContext.Holidays .Include(h => h.States) .Select(h => new { h.HolidayId, h.HolidayName, h.HolidayDate, h.StateId, StateName = h.States.StateName }) .ToListAsync(); return Json(updatedHoliday); } catch (Exception ex) { return BadRequest(ex.Message); } } [HttpGet("GetAllHolidays")] public IActionResult GetAllHolidays() { var holidays = _centralDbContext.Holidays.ToList(); return Ok(holidays); } [HttpDelete("DeleteHoliday/{id}")] public async Task DeleteHoliday(int id) { try { var holiday = await _centralDbContext.Holidays.FindAsync(id); if (holiday == null) { return NotFound("Holiday not found."); } _centralDbContext.Holidays.Remove(holiday); await _centralDbContext.SaveChangesAsync(); return Ok(new { message = "Holiday deleted successfully." }); } catch (Exception ex) { return BadRequest(ex.Message); } } #endregion #region Weekend [HttpGet("GetWeekendDay")] public async Task GetWeekendDay() { try { var weekends = await _centralDbContext.Weekends .Select(w => new { w.WeekendId, w.Day }) .ToListAsync(); return Json(weekends); } catch (Exception ex) { return BadRequest(ex.Message); } } [HttpPost("UpdateWeekend")] public async Task UpdateWeekend([FromBody] List states) { if (!ModelState.IsValid) { return BadRequest(ModelState); } try { foreach (var state in states) { var existingState = await _centralDbContext.States .FirstOrDefaultAsync(s => s.StateId == state.StateId); if (existingState != null) { existingState.WeekendId = state.WeekendId; _centralDbContext.States.Update(existingState); } else { _centralDbContext.States.Add(new StateModel { StateId = state.StateId, StateName = state.StateName, WeekendId = state.WeekendId }); } } await _centralDbContext.SaveChangesAsync(); var updatedWeekend = await _centralDbContext.States .Include(w => w.Weekends) .Select(w => new { w.StateId, w.StateName, w.WeekendId, Day = w.Weekends.Day }) .ToListAsync(); return Json(updatedWeekend); } catch (Exception ex) { return BadRequest(ex.Message); } } [HttpGet("GetAllWeekends")] public IActionResult GetAllWeekends() { var weekends = _centralDbContext.Weekends.ToList(); return Ok(weekends); } [HttpGet("GetStateWeekends")] public async Task GetStateWeekends() { try { var stateWeekends = await _centralDbContext.States .Include(s => s.Weekends) .Where(s => s.WeekendId != null) .Select(s => new { s.StateId, s.StateName, Day = s.Weekends.Day }) .ToListAsync(); return Json(stateWeekends); } catch (Exception ex) { return BadRequest(ex.Message); } } #endregion #region OtRegister [HttpGet("GetStationsByDepartment")] public async Task GetStationsByDepartment([FromQuery] int? departmentId) { if (!departmentId.HasValue) { _logger.LogWarning("GetStationsByDepartment called without a departmentId."); return Ok(new List()); } var stations = await _centralDbContext.Stations .Where(s => s.DepartmentId == departmentId.Value) .Select(s => new { s.StationId, StationName = s.StationName ?? "Unnamed Station" }) .ToListAsync(); return Ok(stations); } [HttpPost("AddOvertime")] public async Task AddOvertimeAsync([FromBody] OvertimeRequestDto request) { _logger.LogInformation("AddOvertimeAsync called."); _logger.LogInformation("Received request: {@Request}", request); if (request == null) { _logger.LogError("Request is null."); return BadRequest("Invalid data."); } try { if (request.UserId == 0) { _logger.LogWarning("No user ID provided."); return BadRequest("User ID is required."); } var user = await _userManager.FindByIdAsync(request.UserId.ToString()); if (user == null) { _logger.LogError("User with ID {UserId} not found for overtime submission.", request.UserId); return Unauthorized("User not found."); } var userRoles = await _userManager.GetRolesAsync(user); var isSuperAdmin = userRoles.Contains("SuperAdmin"); var isSystemAdmin = userRoles.Contains("SystemAdmin"); var userWithDepartment = await _centralDbContext.Users .Include(u => u.Department) .FirstOrDefaultAsync(u => u.Id == request.UserId); int? userDepartmentId = userWithDepartment?.Department?.DepartmentId; bool stationRequired = false; if (userDepartmentId == 2 || userDepartmentId == 3) { stationRequired = true; } else if (isSuperAdmin || isSystemAdmin) { stationRequired = true; } if (stationRequired && (!request.StationId.HasValue || request.StationId.Value <= 0)) { return BadRequest("A station must be selected."); } TimeSpan? officeFrom = string.IsNullOrEmpty(request.OfficeFrom) ? (TimeSpan?)null : TimeSpan.Parse(request.OfficeFrom); TimeSpan? officeTo = string.IsNullOrEmpty(request.OfficeTo) ? (TimeSpan?)null : TimeSpan.Parse(request.OfficeTo); TimeSpan? afterFrom = string.IsNullOrEmpty(request.AfterFrom) ? (TimeSpan?)null : TimeSpan.Parse(request.AfterFrom); TimeSpan? afterTo = string.IsNullOrEmpty(request.AfterTo) ? (TimeSpan?)null : TimeSpan.Parse(request.AfterTo); if ((officeFrom != null && officeTo == null) || (officeFrom == null && officeTo != null)) { return BadRequest("Both Office From and To times must be provided if one is entered."); } if ((afterFrom != null && afterTo == null) || (afterFrom == null && afterTo != null)) { return BadRequest("Both After Office From and To times must be provided if one is entered."); } TimeSpan minAllowedFromMidnightTo = new TimeSpan(16, 30, 0); TimeSpan maxAllowedFromMidnightTo = new TimeSpan(23, 30, 0); if (officeFrom.HasValue && officeTo.HasValue) { if (officeTo == TimeSpan.Zero) { if (officeFrom == TimeSpan.Zero) { return BadRequest("Invalid Office Hour Time: 'From' and 'To' cannot both be 00:00 (midnight)."); } if (officeFrom.Value < minAllowedFromMidnightTo || officeFrom.Value > maxAllowedFromMidnightTo) { return BadRequest("Invalid Office Hour Time: If 'To' is 12:00 am (00:00), 'From' must start between 4:30 pm and 11:30 pm on the same day to be saved."); } } else if (officeTo <= officeFrom) { return BadRequest("Invalid Office Hour Time: 'To' time must be later than 'From' time (same day only)."); } } if (afterFrom.HasValue && afterTo.HasValue) { if (afterTo == TimeSpan.Zero) { if (afterFrom == TimeSpan.Zero) { return BadRequest("Invalid After Office Hour Time: 'From' and 'To' cannot both be 00:00 (midnight)."); } if (afterFrom.Value < minAllowedFromMidnightTo || afterFrom.Value > maxAllowedFromMidnightTo) { return BadRequest("Invalid After Office Hour Time: If 'To' is 12:00 am (00:00), 'From' must start between 4:30 pm and 11:30 pm on the same day to be saved."); } } else if (afterTo <= afterFrom) { return BadRequest("Invalid After Office Hour Time: 'To' time must be later than 'From' time (same day only)."); } } if ((officeFrom == null && officeTo == null) && (afterFrom == null && afterTo == null)) { return BadRequest("Please enter either Office Hours or After Office Hours."); } var newRecord = new OtRegisterModel { OtDate = request.OtDate, OfficeFrom = officeFrom, OfficeTo = officeTo, OfficeBreak = request.OfficeBreak, AfterFrom = afterFrom, AfterTo = afterTo, AfterBreak = request.AfterBreak, StationId = request.StationId, OtDescription = request.OtDescription, OtDays = request.OtDays, UserId = request.UserId, StatusId = request.StatusId }; _centralDbContext.Otregisters.Add(newRecord); await _centralDbContext.SaveChangesAsync(); return Ok(new { message = "Overtime registered successfully." }); } catch (Exception ex) { _logger.LogError(ex, "Error registering overtime for user {UserId}.", request.UserId); return StatusCode(500, $"An error occurred while saving overtime: {ex.InnerException?.Message ?? ex.Message}"); } } [HttpGet("GetUserStateAndHolidays/{userId}")] public async Task GetUserStateAndHolidaysAsync(int userId) { try { var hrSettings = await _centralDbContext.Hrusersetting .Include(h => h.State) .ThenInclude(s => s.Weekends) .Include(h => h.FlexiHour) .Where(h => h.UserId == userId) .FirstOrDefaultAsync(); if (hrSettings?.State == null) { return Ok(new { state = (object)null, publicHolidays = new List() }); } var publicHolidays = await _centralDbContext.Holidays .Where(ph => ph.StateId == hrSettings.StateId && ph.HolidayDate.Year == DateTime.Now.Year) .Select(ph => new { Date = ph.HolidayDate.ToString("yyyy-MM-dd") }) .ToListAsync(); return Ok(new { state = new { stateId = hrSettings.StateId, stateName = hrSettings.State?.StateName, weekendDay = hrSettings.State?.Weekends?.Day, weekendId = hrSettings.State?.WeekendId, flexiHour = hrSettings.FlexiHour?.FlexiHour }, publicHolidays }); } catch (Exception ex) { _logger.LogError(ex, "Error fetching user state and public holidays."); return StatusCode(500, "An error occurred while fetching user state and public holidays."); } } [HttpGet("CheckUserSettings/{userId}")] public async Task CheckUserSettings(int userId) { try { var hrSettings = await _centralDbContext.Hrusersetting .Where(h => h.UserId == userId) .FirstOrDefaultAsync(); if (hrSettings == null) { return Ok(new { isComplete = false }); } if (hrSettings.FlexiHourId == null || hrSettings.StateId == null || hrSettings.ApprovalFlowId == null) { return Ok(new { isComplete = false }); } var rateSetting = await _centralDbContext.Rates .Where(r => r.UserId == userId) .FirstOrDefaultAsync(); if (rateSetting == null) { return Ok(new { isComplete = false }); } else { if (rateSetting.RateValue <= 0.00m) { return Ok(new { isComplete = false }); } } return Ok(new { isComplete = true }); } catch (Exception ex) { _logger.LogError(ex, "Error checking user settings for user {UserId}", userId); return StatusCode(500, $"An error occurred while checking user settings: {ex.Message}"); } } #endregion #region Ot Records [HttpGet("GetUserOvertimeRecords/{userId}")] public IActionResult GetUserOvertimeRecords(int userId) { try { var records = _centralDbContext.Otregisters .Include(o => o.Stations) .Where(o => o.UserId == userId) .OrderByDescending(o => o.OtDate) .Select(o => new { o.OvertimeId, o.OtDate, o.OfficeFrom, o.OfficeTo, o.OfficeBreak, o.AfterFrom, o.AfterTo, o.AfterBreak, o.StationId, StationName = o.Stations != null ? o.Stations.StationName : "N/A", o.OtDescription, o.OtDays, o.UserId }) .ToList(); return Ok(records); } catch (Exception ex) { _logger.LogError(ex, "Failed to fetch OT records."); return StatusCode(500, "Error retrieving OT records."); } } [HttpDelete("DeleteOvertimeRecord/{id}")] public IActionResult DeleteOvertimeRecord(int id) { try { var record = _centralDbContext.Otregisters.FirstOrDefault(o => o.OvertimeId == id); if (record == null) { _logger.LogWarning("Overtime record not found for Id: {OvertimeId}", id); return NotFound("Overtime record not found."); } _centralDbContext.Otregisters.Remove(record); _centralDbContext.SaveChanges(); _logger.LogInformation("Overtime record deleted successfully for Id: {OvertimeId}", id); return Ok(new { message = "Record deleted successfully." }); } catch (Exception ex) { _logger.LogError(ex, "Failed to delete OT record."); return StatusCode(500, "Error deleting OT record."); } } private bool IsAdmin(int userId) { var userRoles = _centralDbContext.UserRoles .Where(ur => ur.UserId == userId) .Join(_centralDbContext.Roles, ur => ur.RoleId, r => r.Id, (ur, r) => r.Name) .ToList(); return userRoles.Any(role => role.Contains("SuperAdmin") || role.Contains("SystemAdmin")); } [HttpPost("SubmitOvertime")] public async Task SubmitOvertime([FromForm] OvertimeSubmissionModel model) { if (model.File == null || model.File.Length == 0) return BadRequest("No file uploaded."); var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int userId)) return Unauthorized(); try { var uploadsFolder = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/Media/Overtime"); if (!Directory.Exists(uploadsFolder)) Directory.CreateDirectory(uploadsFolder); var uniqueFileName = $"{Guid.NewGuid()}_{model.File.FileName}"; var filePath = Path.Combine(uploadsFolder, uniqueFileName); using (var fileStream = new FileStream(filePath, FileMode.Create)) { await model.File.CopyToAsync(fileStream); } var relativePath = $"/Media/Overtime/{uniqueFileName}"; var statusModel = new OtStatusModel { UserId = userId, Month = model.Month, Year = model.Year, FilePath = relativePath, SubmitDate = DateTime.Now, HouStatus = "Pending", HodStatus = "Pending", ManagerStatus = "Pending", HrStatus = "Pending" }; _centralDbContext.Otstatus.Add(statusModel); await _centralDbContext.SaveChangesAsync(); var monthStart = new DateTime(model.Year, model.Month, 1); var monthEnd = monthStart.AddMonths(1); var otRecords = _centralDbContext.Otregisters .Where(r => r.UserId == userId && r.OtDate >= monthStart && r.OtDate < monthEnd) .ToList(); foreach (var record in otRecords) { record.StatusId = statusModel.StatusId; } _centralDbContext.Otregisters.UpdateRange(otRecords); await _centralDbContext.SaveChangesAsync(); var userSetting = _centralDbContext.Hrusersetting.FirstOrDefault(s => s.UserId == userId); if (userSetting != null) { userSetting.ApprovalUpdate = DateTime.Now; _centralDbContext.Hrusersetting.Update(userSetting); } await _centralDbContext.SaveChangesAsync(); return Ok(); } catch (Exception ex) { _logger.LogError(ex, "Failed to submit overtime."); return StatusCode(500, "An error occurred while submitting overtime."); } } [HttpGet("CheckOvertimeSubmitted/{userId}/{month}/{year}")] public IActionResult CheckOvertimeSubmitted(int userId, int month, int year) { try { var latestStatus = _centralDbContext.Otstatus .Where(s => s.UserId == userId && s.Month == month && s.Year == year) .OrderByDescending(s => s.SubmitDate) .FirstOrDefault(); if (latestStatus == null) { return Ok(false); } if (latestStatus.HouStatus?.ToLower() == "rejected" || latestStatus.HodStatus?.ToLower() == "rejected" || latestStatus.ManagerStatus?.ToLower() == "rejected" || latestStatus.HrStatus?.ToLower() == "rejected") { return Ok(false); } if (latestStatus.HouStatus?.ToLower() == "pending" || latestStatus.HodStatus?.ToLower() == "pending" || latestStatus.ManagerStatus?.ToLower() == "pending" || latestStatus.HrStatus?.ToLower() == "pending") { return Ok(true); } return Ok(true); } catch (Exception ex) { _logger.LogError(ex, "Error while checking overtime submission status."); return StatusCode(500, new { error = "Internal server error occurred." }); } } #endregion #region Ot Edit [HttpGet("GetStationsByDepartmentAir")] public IActionResult GetStationsByDepartmentAir() { var stations = _centralDbContext.Stations .Where(s => s.DepartmentId == 2) .Select(s => new { s.StationId, StationName = s.StationName ?? "Unnamed Station" }) .ToList(); return Ok(stations); } [HttpGet("GetStationsByDepartmentMarine")] public IActionResult GetStationsByDepartmentMarine() { var stations = _centralDbContext.Stations .Where(s => s.DepartmentId == 3) .Select(s => new { s.StationId, StationName = s.StationName ?? "Unnamed Station" }) .ToList(); return Ok(stations); } [HttpGet("GetOvertimeRecordById/{id}")] public async Task GetOvertimeRecordById(int id) { var record = await _centralDbContext.Otregisters.FindAsync(id); if (record == null) return NotFound(); return Ok(record); } [HttpGet("GetUserFlexiHour/{userId}")] public async Task GetUserFlexiHour(int userId) { try { var userSetting = await _centralDbContext.Hrusersetting .Include(hs => hs.FlexiHour) .FirstOrDefaultAsync(hs => hs.UserId == userId); if (userSetting == null || userSetting.FlexiHour == null) { _logger.LogWarning("Flexi hour not found for UserId: {UserId}", userId); return NotFound(new { message = "Flexi hour not found for this user." }); } return Ok(new { flexiHour = userSetting.FlexiHour }); } catch (Exception ex) { _logger.LogError(ex, "Error fetching user flexi hour for UserId: {UserId}", userId); return StatusCode(500, new { message = "An error occurred while fetching flexi hour." }); } } [HttpPost] [Route("UpdateOvertimeRecord")] public IActionResult UpdateOvertimeRecord([FromBody] OtRegisterUpdateDto model) { _logger.LogInformation("UpdateOvertimeRecord called. Model: {@Model}", model); try { if (!ModelState.IsValid) { return BadRequest(ModelState); } var timeValidationError = ValidateTimeRanges(model); if (timeValidationError != null) { return BadRequest(new { message = timeValidationError }); } var existing = _centralDbContext.Otregisters.FirstOrDefault(o => o.OvertimeId == model.OvertimeId); if (existing == null) { return NotFound(new { message = "Overtime record not found." }); } existing.OtDate = model.OtDate; existing.OfficeFrom = TimeSpan.TryParse(model.OfficeFrom, out var officeFrom) ? officeFrom : null; existing.OfficeTo = TimeSpan.TryParse(model.OfficeTo, out var officeTo) ? officeTo : null; existing.OfficeBreak = model.OfficeBreak; existing.AfterFrom = TimeSpan.TryParse(model.AfterFrom, out var afterFrom) ? afterFrom : null; existing.AfterTo = TimeSpan.TryParse(model.AfterTo, out var afterTo) ? afterTo : null; existing.AfterBreak = model.AfterBreak; existing.StationId = model.StationId; existing.OtDescription = model.OtDescription; existing.OtDays = model.OtDays; existing.UserId = model.UserId; _centralDbContext.SaveChanges(); return Ok(new { message = "Record updated successfully." }); } catch (Exception ex) { _logger.LogError(ex, "Error updating overtime record."); return StatusCode(500, new { message = "Failed to update record." }); } } private string ValidateTimeRanges(OtRegisterUpdateDto model) { TimeSpan? officeFrom = string.IsNullOrEmpty(model.OfficeFrom) ? (TimeSpan?)null : TimeSpan.Parse(model.OfficeFrom); TimeSpan? officeTo = string.IsNullOrEmpty(model.OfficeTo) ? (TimeSpan?)null : TimeSpan.Parse(model.OfficeTo); TimeSpan? afterFrom = string.IsNullOrEmpty(model.AfterFrom) ? (TimeSpan?)null : TimeSpan.Parse(model.AfterFrom); TimeSpan? afterTo = string.IsNullOrEmpty(model.AfterTo) ? (TimeSpan?)null : TimeSpan.Parse(model.AfterTo); TimeSpan minAllowedFromMidnightTo = new TimeSpan(16, 30, 0); TimeSpan maxAllowedFromMidnightTo = new TimeSpan(23, 30, 0); if (officeFrom.HasValue && officeTo.HasValue) { if (officeTo == TimeSpan.Zero) { if (officeFrom == TimeSpan.Zero) { return "Invalid Office Hour Time: 'From' and 'To' cannot both be 00:00 (midnight)."; } if (officeFrom.Value < minAllowedFromMidnightTo || officeFrom.Value > maxAllowedFromMidnightTo) { return "Invalid Office Hour Time: If 'To' is 12:00 am (00:00), 'From' must start between 4:30 pm and 11:30 pm on the same day to be saved."; } } else if (officeTo <= officeFrom) { return "Invalid Office Hour Time: 'To' time must be later than 'From' time (same day only)."; } } if (afterFrom.HasValue && afterTo.HasValue) { if (afterTo == TimeSpan.Zero) { if (afterFrom == TimeSpan.Zero) { return "Invalid After Office Hour Time: 'From' and 'To' cannot both be 00:00 (midnight)."; } if (afterFrom.Value < minAllowedFromMidnightTo || afterFrom.Value > maxAllowedFromMidnightTo) { return "Invalid After Office Hour Time: If 'To' is 12:00 am (00:00), 'From' must start between 4:30 pm and 11:30 pm on the same day to be saved."; } } else if (afterTo <= afterFrom) { return "Invalid After Office Hour Time: 'To' time must be later than 'From' time (same day only)."; } } if ((officeFrom == null && officeTo == null) && (afterFrom == null && afterTo == null)) { return "Please enter either Office Hours or After Office Hours."; } return null; } #endregion #region OT Status [HttpGet("GetUserOtStatus")] public IActionResult GetUserOtStatus() { var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userIdClaim)) return BadRequest("User ID is not available."); if (!int.TryParse(userIdClaim, out int userId)) return BadRequest("Invalid User ID."); var approvalFlowId = _centralDbContext.Hrusersetting .Where(x => x.UserId == userId) .Select(x => x.ApprovalFlowId) .FirstOrDefault(); bool includeHou = false; bool includeHod = false; bool includeManager = false; bool includeHr = false; if (approvalFlowId != null) { var flow = _centralDbContext.Approvalflow .FirstOrDefault(f => f.ApprovalFlowId == approvalFlowId); if (flow != null) { includeHou = flow.HoU.HasValue; includeHod = flow.HoD.HasValue; includeManager = flow.Manager.HasValue; includeHr = flow.HR.HasValue; } } var otStatuses = _centralDbContext.Otstatus .Where(o => o.UserId == userId) .Select(o => new { o.Month, o.Year, o.SubmitDate, HouStatus = includeHou ? o.HouStatus : null, HodStatus = includeHod ? o.HodStatus : null, ManagerStatus = includeManager ? o.ManagerStatus : null, HrStatus = includeHr ? o.HrStatus : null, o.FilePath, Updated = o.HouUpdate != null || o.HodUpdate != null || o.ManagerUpdate != null || o.HrUpdate != null, o.HouUpdate, o.HodUpdate, o.ManagerUpdate, o.HrUpdate }) .ToList(); return Ok(new { includeHou, includeHod, includeManager, includeHr, otStatuses, hasApprovalFlow = approvalFlowId != null }); } #endregion #region OT Approval [HttpGet("GetPendingApproval")] public IActionResult GetPendingApproval(int month, int year) { var userIdStr = _userManager.GetUserId(User); if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int currentUserId)) return Unauthorized("Invalid or missing user ID"); var flows = _centralDbContext.Approvalflow .Where(f => f.HoU == currentUserId || f.HoD == currentUserId || f.Manager == currentUserId || f.HR == currentUserId) .ToList(); if (!flows.Any()) return Json(new { Roles = new List(), Data = new List(), OverallPendingMonths = new List() }); var flowRoleMap = new Dictionary(); foreach (var flow in flows) { if (flow.HoU == currentUserId) flowRoleMap[flow.ApprovalFlowId] = "HoU"; if (flow.HoD == currentUserId) flowRoleMap[flow.ApprovalFlowId] = "HoD"; if (flow.Manager == currentUserId) flowRoleMap[flow.ApprovalFlowId] = "Manager"; if (flow.HR == currentUserId) flowRoleMap[flow.ApprovalFlowId] = "HR"; } var allRelevantOtEntries = (from status in _centralDbContext.Otstatus join setting in _centralDbContext.Hrusersetting on status.UserId equals setting.UserId where setting.ApprovalFlowId.HasValue && flowRoleMap.Keys.Contains(setting.ApprovalFlowId.Value) select new { status.Month, status.Year, setting.ApprovalFlowId, status.HouStatus, status.HodStatus, status.ManagerStatus, status.HrStatus }).ToList(); var pendingMonthsAndYears = new HashSet(); var allFlows = _centralDbContext.Approvalflow.ToList(); foreach (var entry in allRelevantOtEntries) { var role = flowRoleMap[entry.ApprovalFlowId.Value]; var flow = allFlows.FirstOrDefault(f => f.ApprovalFlowId == entry.ApprovalFlowId); if (flow == null) continue; bool isPendingForCurrentUser = false; if (role == "HoU" && (entry.HouStatus == null || entry.HouStatus == "Pending") && !(entry.HodStatus == "Rejected" || entry.ManagerStatus == "Rejected" || entry.HrStatus == "Rejected")) { isPendingForCurrentUser = true; } else if (role == "HoD" && (entry.HodStatus == null || entry.HodStatus == "Pending") && (!flow.HoU.HasValue || entry.HouStatus == "Approved") && !(entry.HouStatus == "Rejected" || entry.ManagerStatus == "Rejected" || entry.HrStatus == "Rejected")) { isPendingForCurrentUser = true; } else if (role == "Manager" && (entry.ManagerStatus == null || entry.ManagerStatus == "Pending") && (!flow.HoU.HasValue || entry.HouStatus == "Approved") && (!flow.HoD.HasValue || entry.HodStatus == "Approved") && !(entry.HouStatus == "Rejected" || entry.HodStatus == "Rejected" || entry.HrStatus == "Rejected")) { isPendingForCurrentUser = true; } else if (role == "HR" && (entry.HrStatus == null || entry.HrStatus == "Pending") && (!flow.HoU.HasValue || entry.HouStatus == "Approved") && (!flow.HoD.HasValue || entry.HodStatus == "Approved") && (!flow.Manager.HasValue || entry.ManagerStatus == "Approved") && !(entry.HouStatus == "Rejected" || entry.HodStatus == "Rejected" || entry.ManagerStatus == "Rejected")) { isPendingForCurrentUser = true; } if (isPendingForCurrentUser) { pendingMonthsAndYears.Add($"{entry.Month:D2}/{entry.Year}"); } } var otEntriesForSelectedMonth = (from status in _centralDbContext.Otstatus join user in _centralDbContext.Users on status.UserId equals user.Id join setting in _centralDbContext.Hrusersetting on status.UserId equals setting.UserId where status.Month == month && status.Year == year && setting.ApprovalFlowId.HasValue select new { status.StatusId, status.UserId, status.SubmitDate, status.HouStatus, status.HodStatus, status.ManagerStatus, status.HrStatus, setting.ApprovalFlowId, fullName = user.FullName }).ToList(); var processedList = new List(); var distinctRoles = new HashSet(); foreach (var entry in otEntriesForSelectedMonth) { if (!entry.ApprovalFlowId.HasValue || !flowRoleMap.ContainsKey(entry.ApprovalFlowId.Value)) continue; var role = flowRoleMap[entry.ApprovalFlowId.Value]; distinctRoles.Add(role); var flow = allFlows.FirstOrDefault(f => f.ApprovalFlowId == entry.ApprovalFlowId); if (flow == null) continue; bool canApprove = false; string currentUserStatus = "N/A"; if (role == "HoU") { currentUserStatus = entry.HouStatus ?? "Pending"; canApprove = currentUserStatus == "Pending" && !(entry.HodStatus == "Rejected" || entry.ManagerStatus == "Rejected" || entry.HrStatus == "Rejected"); } else if (role == "HoD") { currentUserStatus = entry.HodStatus ?? "Pending"; canApprove = currentUserStatus == "Pending" && (!flow.HoU.HasValue || entry.HouStatus == "Approved") && !(entry.HouStatus == "Rejected" || entry.ManagerStatus == "Rejected" || entry.HrStatus == "Rejected"); } else if (role == "Manager") { currentUserStatus = entry.ManagerStatus ?? "Pending"; canApprove = currentUserStatus == "Pending" && (!flow.HoU.HasValue || entry.HouStatus == "Approved") && (!flow.HoD.HasValue || entry.HodStatus == "Approved") && !(entry.HouStatus == "Rejected" || entry.HodStatus == "Rejected" || entry.HrStatus == "Rejected"); } else if (role == "HR") { currentUserStatus = entry.HrStatus ?? "Pending"; canApprove = currentUserStatus == "Pending" && (!flow.HoU.HasValue || entry.HouStatus == "Approved") && (!flow.HoD.HasValue || entry.HodStatus == "Approved") && (!flow.Manager.HasValue || entry.ManagerStatus == "Approved") && !(entry.HouStatus == "Rejected" || entry.HodStatus == "Rejected" || entry.ManagerStatus == "Rejected"); } bool isOverallRejected = (entry.HouStatus == "Rejected" || entry.HodStatus == "Rejected" || entry.ManagerStatus == "Rejected" || entry.HrStatus == "Rejected"); processedList.Add(new { entry.StatusId, entry.SubmitDate, entry.HouStatus, entry.HodStatus, entry.ManagerStatus, entry.HrStatus, entry.ApprovalFlowId, entry.fullName, Role = role, CanApprove = canApprove, CurrentUserStatus = currentUserStatus, IsOverallRejected = isOverallRejected }); } return Json(new { Roles = distinctRoles.ToList(), Data = processedList, OverallPendingMonths = pendingMonthsAndYears.OrderByDescending(m => m).ToList() }); } [HttpPost("UpdateApprovalStatus")] public IActionResult UpdateApprovalStatus([FromBody] UpdateStatusDto dto) { if (dto == null) return BadRequest("DTO is null or improperly formatted."); if (dto.StatusId == 0 || string.IsNullOrWhiteSpace(dto.Decision)) return BadRequest("Missing StatusId or Decision."); var userIdStr = _userManager.GetUserId(User); if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int currentUserId)) return Unauthorized("Invalid user"); var status = _centralDbContext.Otstatus.FirstOrDefault(s => s.StatusId == dto.StatusId); if (status == null) return NotFound("OT Status not found"); var setting = _centralDbContext.Hrusersetting.FirstOrDefault(s => s.UserId == status.UserId); if (setting == null) return BadRequest("User setting not found"); var flow = _centralDbContext.Approvalflow.FirstOrDefault(f => f.ApprovalFlowId == setting.ApprovalFlowId); if (flow == null) return BadRequest("Approval flow not found"); var now = DateTime.Now; if (status.HouStatus == "Rejected" || status.HodStatus == "Rejected" || status.ManagerStatus == "Rejected" || status.HrStatus == "Rejected") { return BadRequest("This request has already been rejected by a prior approver."); } bool isAuthorizedToAct = false; string currentRoleStatus = ""; if (flow.HoU == currentUserId) { currentRoleStatus = status.HouStatus ?? "Pending"; if (currentRoleStatus == "Pending") isAuthorizedToAct = true; } else if (flow.HoD == currentUserId) { currentRoleStatus = status.HodStatus ?? "Pending"; if (currentRoleStatus == "Pending" && (!flow.HoU.HasValue || status.HouStatus == "Approved")) isAuthorizedToAct = true; } else if (flow.Manager == currentUserId) { currentRoleStatus = status.ManagerStatus ?? "Pending"; if (currentRoleStatus == "Pending" && (!flow.HoU.HasValue || status.HouStatus == "Approved") && (!flow.HoD.HasValue || status.HodStatus == "Approved")) isAuthorizedToAct = true; } else if (flow.HR == currentUserId) { currentRoleStatus = status.HrStatus ?? "Pending"; if (currentRoleStatus == "Pending" && (!flow.HoU.HasValue || status.HouStatus == "Approved") && (!flow.HoD.HasValue || status.HodStatus == "Approved") && (!flow.Manager.HasValue || status.ManagerStatus == "Approved")) isAuthorizedToAct = true; } if (!isAuthorizedToAct) { return BadRequest("You are not authorized to act on this request at this stage or the status has already been set."); } if (dto.Decision.ToLower() == "rejected") { if (flow.HoU == currentUserId) { status.HouStatus = "Rejected"; status.HouSubmitDate = now; } else if (flow.HoD == currentUserId) { status.HodStatus = "Rejected"; status.HodSubmitDate = now; } else if (flow.Manager == currentUserId) { status.ManagerStatus = "Rejected"; status.ManagerSubmitDate = now; } else if (flow.HR == currentUserId) { status.HrStatus = "Rejected"; status.HrSubmitDate = now; } } else if (dto.Decision.ToLower() == "approved") { if (flow.HoU == currentUserId) { status.HouStatus = "Approved"; status.HouSubmitDate = now; } else if (flow.HoD == currentUserId) { status.HodStatus = "Approved"; status.HodSubmitDate = now; } else if (flow.Manager == currentUserId) { status.ManagerStatus = "Approved"; status.ManagerSubmitDate = now; } else if (flow.HR == currentUserId) { status.HrStatus = "Approved"; status.HrSubmitDate = now; } } else { return BadRequest("Invalid decision value."); } _centralDbContext.SaveChanges(); return Ok(new { message = "Status updated successfully." }); } #endregion #region OT Review [HttpGet("GetAllStations")] public IActionResult GetAllStations() { var stations = _centralDbContext.Stations.Select(s => new { s.StationId, s.StationName }).ToList(); return Ok(stations); } [HttpGet("GetOtRecordsByStatusId/{statusId}")] public IActionResult GetOtRecordsByStatusId(int statusId) { var otStatus = _centralDbContext.Otstatus.FirstOrDefault(s => s.StatusId == statusId); if (otStatus == null) return NotFound("OT status not found."); var user = _centralDbContext.Users.FirstOrDefault(u => u.Id == otStatus.UserId); if (user == null) return NotFound("User not found."); var department = _centralDbContext.Departments .Where(d => d.DepartmentId == user.departmentId) .Select(d => d.DepartmentName) .FirstOrDefault(); var userBasicSalary = _centralDbContext.Rates .Where(r => r.UserId == user.Id) .Select(r => r.RateValue) .FirstOrDefault(); var userSetting = _centralDbContext.Hrusersetting .Include(us => us.State) .Include(us => us.Approvalflow) .ThenInclude(af => af.HeadOfUnit) .Include(us => us.FlexiHour) .FirstOrDefault(us => us.UserId == user.Id); if (userSetting?.State == null) return NotFound("User state information not found in HR User Settings."); if (userSetting?.Approvalflow == null) return NotFound("Approval flow information not found for the user."); var publicHolidaysForTable = _centralDbContext.Holidays .Where(h => h.StateId == userSetting.State.StateId) .Select(h => h.HolidayDate.Date.ToString("yyyy-MM-dd")) .ToList(); var otRecords = _centralDbContext.Otregisters .Include(o => o.Stations) .Where(o => o.StatusId == statusId) .ToList() .Select(o => { string dayType; DateTime otDate = o.OtDate.Date; DayOfWeek dayOfWeek = otDate.DayOfWeek; if (publicHolidaysForTable.Contains(otDate.ToString("yyyy-MM-dd"))) { dayType = "Public Holiday"; } else if (userSetting.State.WeekendId == 1) { if (dayOfWeek == DayOfWeek.Friday) dayType = "Off Day"; else if (dayOfWeek == DayOfWeek.Saturday) dayType = "Rest Day"; else dayType = "Normal Day"; } else if (userSetting.State.WeekendId == 2) { if (dayOfWeek == DayOfWeek.Saturday) dayType = "Off Day"; else if (dayOfWeek == DayOfWeek.Sunday) dayType = "Rest Day"; else dayType = "Normal Day"; } else { dayType = "Normal Day"; } return new { o.OvertimeId, o.OtDate, o.OfficeFrom, o.OfficeTo, o.OfficeBreak, o.AfterFrom, o.AfterTo, o.AfterBreak, o.StationId, StationName = o.Stations != null ? o.Stations.StationName : "N/A", o.OtDescription, o.OtDays, o.UserId, Rate = userBasicSalary, // Pass the Basic Salary as 'Rate' DayType = dayType, }; }) .ToList(); var currentLoggedInUserId = GetCurrentLoggedInUserId(); string approverRole = GetApproverRole(currentLoggedInUserId, statusId); bool hasApproverActed = false; switch (approverRole) { case "HoU": hasApproverActed = !string.IsNullOrEmpty(otStatus.HouStatus) && otStatus.HouStatus != "Pending"; break; case "HoD": hasApproverActed = !string.IsNullOrEmpty(otStatus.HodStatus) && otStatus.HodStatus != "Pending"; break; case "Manager": hasApproverActed = !string.IsNullOrEmpty(otStatus.ManagerStatus) && otStatus.ManagerStatus != "Pending"; break; case "HR": hasApproverActed = !string.IsNullOrEmpty(otStatus.HrStatus) && otStatus.HrStatus != "Pending"; break; default: hasApproverActed = true; break; } var result = new { userInfo = new { fullName = user.FullName, departmentName = department, user.departmentId, filePath = otStatus.FilePath, fullNameLower = user.FullName?.ToLower(), flexiHour = userSetting.FlexiHour?.FlexiHour, stateId = userSetting.State.StateId, weekendId = userSetting.State.WeekendId, rate = userBasicSalary }, records = otRecords, isHoU = userSetting.Approvalflow?.HoU == currentLoggedInUserId, otStatus.HouStatus, otStatus.HodStatus, otStatus.ManagerStatus, otStatus.HrStatus, approverRole, hasApproverActed }; return Ok(result); } [HttpGet("GetPublicHolidaysByState/{stateId}")] public IActionResult GetPublicHolidaysByState(int stateId) { var holidays = _centralDbContext.Holidays .Where(h => h.StateId == stateId) .Select(h => new { date = h.HolidayDate.ToString("yyyy-MM-dd"), name = h.HolidayName }) .ToList(); return Ok(holidays); } [HttpPost("UpdateOtRecordByApprover")] public IActionResult UpdateOtRecordByApprover([FromBody] OtRegisterEditDto updatedRecordDto) { if (updatedRecordDto == null) { return BadRequest("Invalid record data."); } var existingRecord = _centralDbContext.Otregisters.FirstOrDefault(o => o.OvertimeId == updatedRecordDto.OvertimeId); if (existingRecord == null) { return NotFound(new { message = "Overtime record not found." }); } var otStatus = _centralDbContext.Otstatus.FirstOrDefault(s => s.StatusId == updatedRecordDto.StatusId); if (otStatus == null) { return NotFound("OT status not found."); } var currentLoggedInUserId = GetCurrentLoggedInUserId(); string approverRole = GetApproverRole(currentLoggedInUserId, otStatus.StatusId); bool hasApproverActed = false; switch (approverRole) { case "HoU": hasApproverActed = !string.IsNullOrEmpty(otStatus.HouStatus) && otStatus.HouStatus != "Pending"; break; case "HoD": hasApproverActed = !string.IsNullOrEmpty(otStatus.HodStatus) && otStatus.HodStatus != "Pending"; break; case "Manager": hasApproverActed = !string.IsNullOrEmpty(otStatus.ManagerStatus) && otStatus.ManagerStatus != "Pending"; break; case "HR": hasApproverActed = !string.IsNullOrEmpty(otStatus.HrStatus) && otStatus.HrStatus != "Pending"; break; } if (hasApproverActed) { return Forbid("You cannot edit this record as you have already acted on this OT submission."); } var originalRecordData = JsonConvert.SerializeObject(existingRecord, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }); existingRecord.OtDate = updatedRecordDto.OtDate; existingRecord.OfficeFrom = ParseTimeStringToTimeSpan(updatedRecordDto.OfficeFrom); existingRecord.OfficeTo = ParseTimeStringToTimeSpan(updatedRecordDto.OfficeTo); existingRecord.OfficeBreak = updatedRecordDto.OfficeBreak; existingRecord.AfterFrom = ParseTimeStringToTimeSpan(updatedRecordDto.AfterFrom); existingRecord.AfterTo = ParseTimeStringToTimeSpan(updatedRecordDto.AfterTo); existingRecord.AfterBreak = updatedRecordDto.AfterBreak; existingRecord.StationId = updatedRecordDto.StationId; existingRecord.OtDescription = updatedRecordDto.OtDescription; _centralDbContext.SaveChanges(); var updateLog = new OtUpdateLog { ApproverRole = approverRole, ApproverUserId = currentLoggedInUserId, UpdateTimestamp = DateTime.Now, ChangeType = "Edit", BeforeEdit = JsonConvert.DeserializeObject(originalRecordData), AfterEdit = updatedRecordDto }; var logJson = JsonConvert.SerializeObject(updateLog); switch (approverRole) { case "HoU": otStatus.HouUpdate = AppendUpdateLog(otStatus.HouUpdate, logJson); break; case "HoD": otStatus.HodUpdate = AppendUpdateLog(otStatus.HodUpdate, logJson); break; case "Manager": otStatus.ManagerUpdate = AppendUpdateLog(otStatus.ManagerUpdate, logJson); break; case "HR": otStatus.HrUpdate = AppendUpdateLog(otStatus.HrUpdate, logJson); break; default: return Unauthorized("You are not authorized to edit this record."); } _centralDbContext.SaveChanges(); return Ok(new { message = "Overtime record updated successfully and changes logged." }); } [HttpDelete("DeleteOvertimeInOtReview/{id}")] public IActionResult DeleteOvertimeInOtReview(int id) { var recordToDelete = _centralDbContext.Otregisters.FirstOrDefault(o => o.OvertimeId == id); if (recordToDelete == null) { return NotFound(new { message = "Overtime record not found." }); } var statusIdForRecord = recordToDelete.StatusId; if (!statusIdForRecord.HasValue) { return BadRequest("Overtime record is not associated with a status."); } var otStatus = _centralDbContext.Otstatus.FirstOrDefault(s => s.StatusId == statusIdForRecord.Value); if (otStatus == null) { return NotFound("OT status for this record not found."); } var currentLoggedInUserId = GetCurrentLoggedInUserId(); string approverRole = GetApproverRole(currentLoggedInUserId, statusIdForRecord.Value); bool hasApproverActed = false; switch (approverRole) { case "HoU": hasApproverActed = !string.IsNullOrEmpty(otStatus.HouStatus) && otStatus.HouStatus != "Pending"; break; case "HoD": hasApproverActed = !string.IsNullOrEmpty(otStatus.HodStatus) && otStatus.HodStatus != "Pending"; break; case "Manager": hasApproverActed = !string.IsNullOrEmpty(otStatus.ManagerStatus) && otStatus.ManagerStatus != "Pending"; break; case "HR": hasApproverActed = !string.IsNullOrEmpty(otStatus.HrStatus) && otStatus.HrStatus != "Pending"; break; } if (hasApproverActed) { return Forbid("You cannot delete this record as you have already acted on this OT submission."); } var deletedRecordData = JsonConvert.SerializeObject(recordToDelete, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }); _centralDbContext.Otregisters.Remove(recordToDelete); _centralDbContext.SaveChanges(); var deleteLog = new OtUpdateLog { ApproverRole = approverRole, ApproverUserId = currentLoggedInUserId, UpdateTimestamp = DateTime.Now, ChangeType = "Delete", DeletedRecord = JsonConvert.DeserializeObject(deletedRecordData) }; var logJson = JsonConvert.SerializeObject(deleteLog); switch (approverRole) { case "HoU": otStatus.HouUpdate = AppendUpdateLog(otStatus.HouUpdate, logJson); break; case "HoD": otStatus.HodUpdate = AppendUpdateLog(otStatus.HodUpdate, logJson); break; case "Manager": otStatus.ManagerUpdate = AppendUpdateLog(otStatus.ManagerUpdate, logJson); break; case "HR": otStatus.HrUpdate = AppendUpdateLog(otStatus.HrUpdate, logJson); break; default: Console.WriteLine($"Unauthorized deletion attempt by user {currentLoggedInUserId} for status {statusIdForRecord.Value}"); break; } _centralDbContext.SaveChanges(); return Ok(new { message = "Overtime record deleted successfully and deletion logged." }); } private int GetCurrentLoggedInUserId() { if (User.Identity.IsAuthenticated) { var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier); if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId)) { return userId; } } return -1; } private string GetApproverRole(int currentUserId, int statusId) { var otStatus = _centralDbContext.Otstatus.FirstOrDefault(s => s.StatusId == statusId); if (otStatus == null) { return "Unknown: Status not found"; } var submittedByUserId = otStatus.UserId; var hrUserSetting = _centralDbContext.Hrusersetting .Include(hus => hus.Approvalflow) .FirstOrDefault(hus => hus.UserId == submittedByUserId); if (hrUserSetting?.Approvalflow == null) { return "Unknown: Approval flow not configured for user"; } var approvalFlow = hrUserSetting.Approvalflow; if (approvalFlow.HoU == currentUserId) return "HoU"; if (approvalFlow.HoD == currentUserId) return "HoD"; if (approvalFlow.Manager == currentUserId) return "Manager"; if (approvalFlow.HR == currentUserId) return "HR"; return "Unknown: Not an assigned approver"; } private string AppendUpdateLog(string? existingLogJson, string newLogJson) { List logs; if (!string.IsNullOrEmpty(existingLogJson)) { try { logs = JsonConvert.DeserializeObject>(existingLogJson); if (logs == null) logs = new List(); } catch { logs = new List(); } } else { logs = new List(); } var newLogEntry = JsonConvert.DeserializeObject(newLogJson); if (newLogEntry != null) { logs.Add(newLogEntry); } return JsonConvert.SerializeObject(logs); } private TimeSpan? ParseTimeStringToTimeSpan(string? timeString) { if (string.IsNullOrEmpty(timeString)) { return null; } if (TimeSpan.TryParse(timeString, out TimeSpan result)) { return result; } return null; } #endregion #region OT Review/ OT Record PDF Excel [HttpGet("GetOvertimePdfByStatusId/{statusId}")] public IActionResult GetOvertimePdfByStatusId(int statusId) { var otStatus = _centralDbContext.Otstatus.FirstOrDefault(s => s.StatusId == statusId); if (otStatus == null) return NotFound("OT status not found."); var user = _centralDbContext.Users .Include(u => u.Department) .FirstOrDefault(u => u.Id == otStatus.UserId); if (user == null) return NotFound("User not found."); var userRate = _centralDbContext.Rates .Where(r => r.UserId == user.Id) .OrderByDescending(r => r.LastUpdated) .FirstOrDefault()?.RateValue ?? 0m; var otRecords = _centralDbContext.Otregisters .Include(o => o.Stations) .Where(o => o.StatusId == statusId) .ToList(); DateTime? selectedMonth = otRecords.Any() ? otRecords.First().OtDate : DateTime.Now; byte[]? logoImage = null; var logoPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images", "logo.jpg"); if (System.IO.File.Exists(logoPath)) { logoImage = System.IO.File.ReadAllBytes(logoPath); } var currentLoggedInUserId = GetCurrentLoggedInUserId(); var userSetting = _centralDbContext.Hrusersetting .Include(us => us.Approvalflow) .Include(us => us.FlexiHour) .FirstOrDefault(us => us.UserId == user.Id); // Determine if the current logged-in user is HoU, HoD, or Manager for the OT user bool hideSalaryDetails = false; if (userSetting?.Approvalflow != null) { if (userSetting.Approvalflow.HoU == currentLoggedInUserId || userSetting.Approvalflow.HoD == currentLoggedInUserId || userSetting.Approvalflow.Manager == currentLoggedInUserId) { hideSalaryDetails = true; } } string? flexiHour = userSetting?.FlexiHour?.FlexiHour; var approvedSignatures = new List(); var approvalFlow = userSetting?.Approvalflow; if (approvalFlow != null) { var approvalStages = new List<(int? approverId, string statusField, DateTime? submitDate, UserModel? approverUser)> { (approvalFlow.HoU, otStatus.HouStatus, otStatus.HouSubmitDate, _centralDbContext.Users.FirstOrDefault(u => u.Id == approvalFlow.HoU)), (approvalFlow.HoD, otStatus.HodStatus, otStatus.HodSubmitDate, _centralDbContext.Users.FirstOrDefault(u => u.Id == approvalFlow.HoD)), (approvalFlow.Manager, otStatus.ManagerStatus, otStatus.ManagerSubmitDate, _centralDbContext.Users.FirstOrDefault(u => u.Id == approvalFlow.Manager)), (approvalFlow.HR, otStatus.HrStatus, otStatus.HrSubmitDate, _centralDbContext.Users.FirstOrDefault(u => u.Id == approvalFlow.HR)) }; foreach (var stage in approvalStages .Where(s => s.approverUser != null && s.statusField == "Approved" && s.submitDate.HasValue) .OrderBy(s => s.submitDate)) { byte[]? signatureImageBytes = null; var staffSign = _centralDbContext.Staffsign .FirstOrDefault(ss => ss.UserId == stage.approverId.Value); approvedSignatures.Add(new ApprovalSignatureData { ApproverName = stage.approverUser.FullName, SignatureImage = signatureImageBytes, ApprovedDate = stage.submitDate }); } } var pdfGenerator = new OvertimePDF(_centralDbContext); // Pass the new hideSalaryDetails flag to the PDF generator var stream = pdfGenerator.GenerateOvertimeTablePdf(otRecords, user, userRate, selectedMonth, logoImage, hideSalaryDetails, flexiHour, approvedSignatures); string fileName = $"OvertimeReport_{user.FullName}_{DateTime.Now:yyyyMMdd}.pdf"; return File(stream.ToArray(), "application/pdf", fileName); } [HttpGet("GetUserOvertimePdf/{userId}/{month}/{year}")] public IActionResult GetUserOvertimePdf(int userId, int month, int year) { var user = _centralDbContext.Users .Include(u => u.Department) .FirstOrDefault(u => u.Id == userId); if (user == null) return NotFound("User not found."); var userRate = _centralDbContext.Rates .Where(r => r.UserId == user.Id) .OrderByDescending(r => r.LastUpdated) .FirstOrDefault()?.RateValue ?? 0m; var otRecords = _centralDbContext.Otregisters .Include(o => o.Stations) .Where(o => o.UserId == userId && o.OtDate.Month == month && o.OtDate.Year == year) .Select(o => new OtRegisterModel { OtDate = o.OtDate, OfficeFrom = o.OfficeFrom, OfficeTo = o.OfficeTo, OfficeBreak = o.OfficeBreak, AfterFrom = o.AfterFrom, AfterTo = o.AfterTo, AfterBreak = o.AfterBreak, StationId = o.StationId, Stations = o.Stations, OtDescription = o.OtDescription, OtDays = o.OtDays }) .ToList(); DateTime? selectedMonth = new DateTime(year, month, 1); byte[]? logoImage = null; var logoPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images", "logo.jpg"); if (System.IO.File.Exists(logoPath)) { logoImage = System.IO.File.ReadAllBytes(logoPath); } var flexiHour = _centralDbContext.Hrusersetting .Include(us => us.FlexiHour) .FirstOrDefault(us => us.UserId == userId)?.FlexiHour?.FlexiHour; var pdfGenerator = new OvertimePDF(_centralDbContext); var stream = pdfGenerator.GenerateSimpleOvertimeTablePdf(otRecords, user, userRate, selectedMonth, logoImage, flexiHour); string fileName = $"OvertimeReport_{user.FullName}_{year}_{month}.pdf"; return File(stream.ToArray(), "application/pdf", fileName); } [HttpGet("GetOvertimeExcelByStatusId/{statusId}")] public IActionResult GetOvertimeExcelByStatusId(int statusId) { var otStatus = _centralDbContext.Otstatus.FirstOrDefault(s => s.StatusId == statusId); if (otStatus == null) return NotFound("OT status not found."); var user = _centralDbContext.Users .Include(u => u.Department) .FirstOrDefault(u => u.Id == otStatus.UserId); if (user == null) return NotFound("User not found."); var userRate = _centralDbContext.Rates .Where(r => r.UserId == user.Id) .OrderByDescending(r => r.LastUpdated) .FirstOrDefault()?.RateValue ?? 0m; var otRecords = _centralDbContext.Otregisters .Include(o => o.Stations) .Where(o => o.StatusId == statusId) .ToList(); DateTime? selectedMonth = otRecords.Any() ? otRecords.First().OtDate : DateTime.Now; var currentLoggedInUserId = GetCurrentLoggedInUserId(); var userSetting = _centralDbContext.Hrusersetting .Include(us => us.Approvalflow) .Include(us => us.FlexiHour) .FirstOrDefault(us => us.UserId == user.Id); bool isHoU = false; bool isHoD = false; // Initialize isHoD bool isManager = false; // Initialize isManager if (userSetting?.Approvalflow != null) { if (userSetting.Approvalflow.HoU == currentLoggedInUserId) { isHoU = true; } if (userSetting.Approvalflow.HoD == currentLoggedInUserId) // Check if current user is HoD { isHoD = true; } if (userSetting.Approvalflow.Manager == currentLoggedInUserId) // Check if current user is Manager { isManager = true; } } string? flexiHour = userSetting?.FlexiHour?.FlexiHour; var excelGenerator = new OvertimeExcel(_centralDbContext, _env); var logoPath = Path.Combine(_env.WebRootPath, "images", "logo.jpg"); byte[]? logoImage = System.IO.File.Exists(logoPath) ? System.IO.File.ReadAllBytes(logoPath) : null; var stream = excelGenerator.GenerateOvertimeExcel( otRecords, user, userRate, selectedMonth, isHoU, isHoD, // Pass isHoD isManager, // Pass isManager flexiHour, logoImage, isSimplifiedExport: false, otStatus ); string fileName = $"OvertimeReport_{user.FullName}_{DateTime.Now:yyyyMMdd}.xlsx"; return File(stream.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName); } [HttpGet("GenerateUserOvertimeExcel/{userId}/{month}/{year}")] public IActionResult GenerateUserOvertimeExcel(int userId, int month, int year) { try { var user = _centralDbContext.Users .Include(u => u.Department) .FirstOrDefault(u => u.Id == userId); if (user == null) return NotFound("User not found."); var userRate = _centralDbContext.Rates .Where(r => r.UserId == user.Id) .OrderByDescending(r => r.LastUpdated) .FirstOrDefault()?.RateValue ?? 0m; var userSetting = _centralDbContext.Hrusersetting .Include(us => us.FlexiHour) .Include(us => us.Approvalflow) // Include Approvalflow to check roles .FirstOrDefault(us => us.UserId == userId); string flexiHour = userSetting?.FlexiHour?.FlexiHour; var startDate = new DateTime(year, month, 1); var endDate = startDate.AddMonths(1); var otRecords = _centralDbContext.Otregisters .Include(o => o.Stations) .Where(o => o.UserId == userId && o.OtDate >= startDate && o.OtDate < endDate) .ToList(); var currentLoggedInUserId = GetCurrentLoggedInUserId(); bool isHoU = false; bool isHoD = false; // Initialize isHoD bool isManager = false; // Initialize isManager if (userSetting?.Approvalflow != null) { if (userSetting.Approvalflow.HoU == currentLoggedInUserId) { isHoU = true; } if (userSetting.Approvalflow.HoD == currentLoggedInUserId) // Check if current user is HoD { isHoD = true; } if (userSetting.Approvalflow.Manager == currentLoggedInUserId) // Check if current user is Manager { isManager = true; } } var excelGenerator = new OvertimeExcel(_centralDbContext, _env); var logoPath = Path.Combine(_env.WebRootPath, "images", "logo.jpg"); byte[]? logoImage = System.IO.File.Exists(logoPath) ? System.IO.File.ReadAllBytes(logoPath) : null; var stream = excelGenerator.GenerateOvertimeExcel( otRecords, user, userRate, startDate, isHoU, isHoD, // Pass isHoD isManager, // Pass isManager flexiHour: flexiHour, logoImage: logoImage, isSimplifiedExport: true ); string fileName = $"OvertimeReport_{user.FullName}_{month}_{year}.xlsx"; return File(stream.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName); } catch (Exception ex) { _logger.LogError(ex, "Error generating user overtime Excel"); return StatusCode(500, "Error generating Excel file"); } } #endregion } }