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; 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 OvertimePdfService _pdfService; private readonly IWebHostEnvironment _env; private readonly OvertimeExcelService _excelService; public OvertimeAPI(ILogger logger, CentralSystemContext centralDbContext, UserManager userManager, OvertimePdfService pdfService, IWebHostEnvironment env, OvertimeExcelService excelService) { _logger = logger; _centralDbContext = centralDbContext; _userManager = userManager; _pdfService = pdfService; _env = env; _excelService = excelService; } #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); } } #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(); // Log this data to inspect the response Console.WriteLine(JsonConvert.SerializeObject(result)); // Debugging log 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; } else { // Handle the case where a user setting doesn't exist. // You might want to create a new one with a default ApprovalFlowId or return an error. _centralDbContext.Hrusersetting.Add(new HrUserSettingModel { UserId = update.UserId, StateId = update.StateId, // Consider setting a default ApprovalFlowId here if needed }); } } 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}" }); } } [HttpPut("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."); } // Update fields 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) { // Log the exception 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." }); } _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) { // Corrected: Updating WeekendId existingState.WeekendId = state.WeekendId; _centralDbContext.States.Update(existingState); } else { // Ensure new states are added correctly _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 IActionResult GetStationsByDepartment() { var stations = _centralDbContext.Stations .Where(s => s.DepartmentId == 2) .Select(s => new { s.StationId, StationName = s.StationName ?? "Unnamed Station" }) .ToList(); 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."); } // Parse times (make them optional) 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); // Office time validation: if officeFrom is set, officeTo must be set too, and vice versa if ((officeFrom != null && officeTo == null) || (officeFrom == null && officeTo != null)) { return BadRequest("Both Office From and To must be filled if one is provided."); } // No need for specific validation for AfterFrom and AfterTo being both present // If one is null and the other isn't, that's acceptable now. // Map to DB model (continue using null for optional fields) var newRecord = new OtRegisterModel { OtDate = request.OtDate, OfficeFrom = officeFrom, OfficeTo = officeTo, OfficeBreak = request.OfficeBreak ?? null, // Make OfficeBreak optional AfterFrom = afterFrom, AfterTo = afterTo, AfterBreak = request.AfterBreak ?? null, // Make AfterBreak optional StationId = request.StationId, OtDescription = request.OtDescription, OtDays = request.OtDays, UserId = request.UserId, StatusId = request.StatusId // Pass StatusId as it is, which can be null }; _centralDbContext.Otregisters.Add(newRecord); await _centralDbContext.SaveChangesAsync(); return Ok(new { message = "Overtime registered successfully." }); } catch (Exception ex) { _logger.LogError(ex, "Error registering overtime."); 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) .Where(h => h.UserId == userId) .FirstOrDefaultAsync(); if (hrSettings?.State == null) { return Ok(new { state = (object)null, publicHolidays = new List() }); // Or handle no state differently } // Fetch public holidays for the user's state and the current year (example) 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 }, 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."); } } #endregion #region Ot Records [HttpGet("GetUserOvertimeRecords/{userId}")] public IActionResult GetUserOvertimeRecords(int userId) { try { var records = _centralDbContext.Otregisters .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."); } } [HttpGet("GenerateOvertimePdf")] public IActionResult GenerateOvertimePdf(int month, int year) { var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int userId)) return Unauthorized(); var user = _centralDbContext.Users .Include(u => u.Department) .FirstOrDefault(u => u.Id == userId); if (user == null) return NotFound("User not found."); var fullName = user.FullName; var departmentId = user.departmentId ?? 0; var departmentName = user.Department?.DepartmentName ?? "N/A"; var records = _centralDbContext.Otregisters .Include(o => o.Stations) .Where(o => o.UserId == userId && o.OtDate.Month == month && o.OtDate.Year == year) .ToList(); var hrUserSetting = _centralDbContext.Hrusersetting.FirstOrDefault(h => h.UserId == userId); // StateId var userStateId = hrUserSetting?.StateId ?? 0; // WeekendId var weekendId = _centralDbContext.States .Where(s => s.StateId == userStateId) .Select(s => s.WeekendId) .FirstOrDefault() ?? 2; // Default Sat-Sun if null // Public Holidays var publicHolidays = _centralDbContext.Holidays .Where(c => c.StateId == userStateId) .ToList(); // Step 1: Generate all days of the month var daysInMonth = DateTime.DaysInMonth(year, month); var allDays = Enumerable.Range(1, daysInMonth) .Select(day => new DateTime(year, month, day)) .ToList(); // Step 2: Merge records with missing days var mergedRecords = new List(); foreach (var date in allDays) { var dayRecords = records .Where(r => r.OtDate.Date == date.Date) .OrderBy(r => r.OfficeFrom) .ToList(); if (dayRecords.Any()) { mergedRecords.AddRange(dayRecords); } else { mergedRecords.Add(new OtRegisterModel { OtDate = date, OtDays = date.DayOfWeek.ToString(), OtDescription = "", }); } } byte[]? logoImage = null; var logoPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images", "logo.jpg"); logoImage = System.IO.File.Exists(logoPath) ? System.IO.File.ReadAllBytes(logoPath) : null; var stream = _pdfService.GenerateOvertimeTablePdf( mergedRecords, departmentId, fullName, departmentName, userStateId, weekendId, publicHolidays, isAdminUser: IsAdmin(userId), logoImage ); return File(stream, "application/pdf", $"OvertimeRecords_{year}_{month}.pdf"); } 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")); } [HttpGet("GenerateOvertimeExcel")] public IActionResult GenerateOvertimeExcel(int month, int year) { var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int userId)) return Unauthorized(); var user = _centralDbContext.Users .Include(u => u.Department) .FirstOrDefault(u => u.Id == userId); if (user == null) return NotFound("User not found."); var fullName = user.FullName; var departmentId = user.departmentId ?? 0; var departmentName = user.Department?.DepartmentName ?? "N/A"; var records = _centralDbContext.Otregisters .Include(o => o.Stations) .Where(o => o.UserId == userId && o.OtDate.Month == month && o.OtDate.Year == year) .ToList(); var hrUserSetting = _centralDbContext.Hrusersetting.FirstOrDefault(h => h.UserId == userId); var userStateId = hrUserSetting?.StateId ?? 0; var weekendId = _centralDbContext.States .Where(s => s.StateId == userStateId) .Select(s => s.WeekendId) .FirstOrDefault() ?? 2; var publicHolidays = _centralDbContext.Holidays .Where(c => c.StateId == userStateId) .ToList(); var daysInMonth = DateTime.DaysInMonth(year, month); var allDays = Enumerable.Range(1, daysInMonth) .Select(day => new DateTime(year, month, day)) .ToList(); var mergedRecords = new List(); foreach (var date in allDays) { var dayRecords = records .Where(r => r.OtDate.Date == date.Date) .OrderBy(r => r.OfficeFrom) .ToList(); if (dayRecords.Any()) { mergedRecords.AddRange(dayRecords); } else { mergedRecords.Add(new OtRegisterModel { OtDate = date, OtDays = date.DayOfWeek.ToString(), OtDescription = "", }); } } var logoPath = Path.Combine(_env.WebRootPath, "images", "logo.jpg"); byte[]? logoImage = System.IO.File.Exists(logoPath) ? System.IO.File.ReadAllBytes(logoPath) : null; var stream = _excelService.GenerateOvertimeExcel( mergedRecords, departmentId, fullName, departmentName, userStateId, weekendId, publicHolidays, isAdminUser: IsAdmin(userId), logoImage: logoImage ); return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", $"OvertimeRecords_{year}_{month}.xlsx"); } [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 = Path.Combine("Media", "Overtime", uniqueFileName).Replace("\\", "/"); // Create a NEW OtStatusModel for the resubmission 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(); // Save the new OtStatus record to get its StatusId // Update StatusId in OtRegister records for the current month/year 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(); // Update HrUserSetting with the NEW StatusId 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 { // Get the latest OtStatus record for the user, month, and year 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); // Not submitted yet } // Check if the latest submission has been rejected at any level if (latestStatus.HouStatus?.ToLower() == "rejected" || latestStatus.HodStatus?.ToLower() == "rejected" || latestStatus.ManagerStatus?.ToLower() == "rejected" || latestStatus.HrStatus?.ToLower() == "rejected") { return Ok(false); // Latest submission was rejected, enable submit } // If not rejected, check if it's in a pending state (new submission) if (latestStatus.HouStatus?.ToLower() == "pending" || latestStatus.HodStatus?.ToLower() == "pending" || latestStatus.ManagerStatus?.ToLower() == "pending" || latestStatus.HrStatus?.ToLower() == "pending") { return Ok(true); // Newly submitted or resubmitted, disable submit } // If not pending and not rejected, it implies it's fully approved or in a final rejected state return Ok(true); // Disable submit } 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("GetOvertimeRecordById/{id}")] public async Task GetOvertimeRecordById(int id) { var record = await _centralDbContext.Otregisters.FindAsync(id); if (record == null) return NotFound(); return Ok(record); } [HttpPost] [Route("UpdateOvertimeRecord")] public IActionResult UpdateOvertimeRecord([FromForm] OtRegisterModel model) { _logger.LogInformation("UpdateOvertimeRecord called. Model: {@Model}", model); try { if (!ModelState.IsValid) { _logger.LogWarning("ModelState is invalid. Errors: {@ModelStateErrors}", ModelState.Values.SelectMany(v => v.Errors)); return BadRequest(ModelState); } var existing = _centralDbContext.Otregisters.FirstOrDefault(o => o.OvertimeId == model.OvertimeId); if (existing == null) { _logger.LogWarning("Overtime record not found for Id: {OvertimeId}", model.OvertimeId); return NotFound("Overtime record not found."); } _logger.LogInformation("Existing record found: {@ExistingRecord}", existing); existing.OtDate = model.OtDate; existing.OfficeFrom = model.OfficeFrom; existing.OfficeTo = model.OfficeTo; existing.OfficeBreak = model.OfficeBreak; existing.AfterFrom = model.AfterFrom; existing.AfterTo = model.AfterTo; existing.AfterBreak = model.AfterBreak; existing.StationId = model.StationId; existing.OtDescription = model.OtDescription; existing.OtDays = model.OtDays; existing.UserId = model.UserId; _centralDbContext.SaveChanges(); _logger.LogInformation("Overtime record updated successfully for Id: {OvertimeId}", model.OvertimeId); return Ok(new { message = "Record updated successfully." }); } catch (Exception ex) { _logger.LogError(ex, "Error updating overtime record. Stack Trace: {StackTrace}", ex.StackTrace); return StatusCode(500, "Failed to update record."); } } #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(); var flow = _centralDbContext.Approvalflow .FirstOrDefault(f => f.ApprovalFlowId == approvalFlowId); if (flow == null) return BadRequest("Approval flow not found."); bool includeHou = flow.HoU.HasValue; bool includeHod = flow.HoD.HasValue; bool includeManager = flow.Manager.HasValue; bool 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 }) .ToList(); return Ok(new { includeHou, includeHod, includeManager, includeHr, otStatuses }); } #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"); // Get all flows where the user is an approver var flows = _centralDbContext.Approvalflow .Where(f => f.HoU == currentUserId || f.HoD == currentUserId || f.Manager == currentUserId || f.HR == currentUserId) .ToList(); if (!flows.Any()) return Unauthorized("You are not assigned to any approval flow."); // Map flow ID to role (the role user is in for that flow) 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"; } // Load all OT status entries and their associated approval flow IDs var otEntriesWithFlow = (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.SubmitDate, status.HouStatus, status.HodStatus, status.ManagerStatus, status.HrStatus, setting.ApprovalFlowId, fullName = user.FullName }).ToList(); var filteredList = new List(); var distinctRoles = new HashSet(); foreach (var entry in otEntriesWithFlow) { if (!flowRoleMap.ContainsKey(entry.ApprovalFlowId.Value)) continue; // User is not an approver for this flow var role = flowRoleMap[entry.ApprovalFlowId.Value]; distinctRoles.Add(role); var flow = _centralDbContext.Approvalflow.FirstOrDefault(f => f.ApprovalFlowId == entry.ApprovalFlowId); if (flow == null) continue; // If any earlier status is rejected, do not allow further approvals if (entry.HouStatus == "Rejected" || entry.HodStatus == "Rejected" || entry.ManagerStatus == "Rejected" || entry.HrStatus == "Rejected") continue; bool shouldShow = false; // Show if the current user still needs to act if ((role == "HoU" && entry.HouStatus == "Pending") || (role == "HoD" && (!flow.HoU.HasValue || entry.HouStatus == "Approved") && entry.HodStatus == "Pending") || (role == "Manager" && (!flow.HoU.HasValue || entry.HouStatus == "Approved") && (!flow.HoD.HasValue || entry.HodStatus == "Approved") && entry.ManagerStatus == "Pending") || (role == "HR" && (!flow.HoU.HasValue || entry.HouStatus == "Approved") && (!flow.HoD.HasValue || entry.HodStatus == "Approved") && (!flow.Manager.HasValue || entry.ManagerStatus == "Approved") && entry.HrStatus == "Pending")) { shouldShow = true; } // Also show if the current user has already acted (approved or rejected) else if ((role == "HoU" && (entry.HouStatus == "Approved" || entry.HouStatus == "Rejected")) || (role == "HoD" && (entry.HodStatus == "Approved" || entry.HodStatus == "Rejected")) || (role == "Manager" && (entry.ManagerStatus == "Approved" || entry.ManagerStatus == "Rejected")) || (role == "HR" && (entry.HrStatus == "Approved" || entry.HrStatus == "Rejected"))) { shouldShow = true; } if (shouldShow) { filteredList.Add(entry); } } return Json(new { Roles = distinctRoles.ToList(), Data = filteredList }); } [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 (dto.Decision.ToLower() == "rejected") { if (flow.HoU == currentUserId && status.HouStatus == "Pending") { status.HouStatus = "Rejected"; status.HouSubmitDate = now; } else if (flow.HoD == currentUserId && status.HodStatus == "Pending" && (!flow.HoU.HasValue || status.HouStatus == "Approved")) { status.HodStatus = "Rejected"; status.HodSubmitDate = now; } else if (flow.Manager == currentUserId && status.ManagerStatus == "Pending" && (!flow.HoU.HasValue || status.HouStatus == "Approved") && (!flow.HoD.HasValue || status.HodStatus == "Approved")) { status.ManagerStatus = "Rejected"; status.ManagerSubmitDate = now; } else if (flow.HR == currentUserId && status.HrStatus == "Pending" && (!flow.HoU.HasValue || status.HouStatus == "Approved") && (!flow.HoD.HasValue || status.HodStatus == "Approved") && (!flow.Manager.HasValue || status.ManagerStatus == "Approved")) { status.HrStatus = "Rejected"; status.HrSubmitDate = now; } else { return BadRequest("Not authorized to reject this request or already decided based on the flow."); } } else if (dto.Decision.ToLower() == "approved") { if (flow.HoU == currentUserId && status.HouStatus == "Pending") { status.HouStatus = "Approved"; status.HouSubmitDate = now; } else if (flow.HoD == currentUserId && status.HodStatus == "Pending" && (!flow.HoU.HasValue || status.HouStatus == "Approved")) { status.HodStatus = "Approved"; status.HodSubmitDate = now; } else if (flow.Manager == currentUserId && status.ManagerStatus == "Pending" && (!flow.HoU.HasValue || status.HouStatus == "Approved") && (!flow.HoD.HasValue || status.HodStatus == "Approved")) { status.ManagerStatus = "Approved"; status.ManagerSubmitDate = now; } else if (flow.HR == currentUserId && status.HrStatus == "Pending" && (!flow.HoU.HasValue || status.HouStatus == "Approved") && (!flow.HoD.HasValue || status.HodStatus == "Approved") && (!flow.Manager.HasValue || status.ManagerStatus == "Approved")) { status.HrStatus = "Approved"; status.HrSubmitDate = now; } else { return BadRequest("Not authorized to approve this request or already decided based on the flow."); } } else { return BadRequest("Invalid decision value."); } _centralDbContext.SaveChanges(); return Ok(new { message = "Status updated successfully." }); } #endregion #region OT Review [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 otRecords = _centralDbContext.Otregisters .Include(o => o.Stations) .Where(o => o.StatusId == statusId) .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(); var result = new { userInfo = new { fullName = user.FullName, departmentName = department, filePath = otStatus.FilePath }, records = otRecords }; return Ok(result); } [HttpDelete("DeleteOvertimeInOtReview/{id}")] public IActionResult DeleteOvertimeInOtReview(int id) { var record = _centralDbContext.Otregisters.FirstOrDefault(o => o.OvertimeId == id); if (record == null) return NotFound(new { message = "Overtime record not found." }); _centralDbContext.Otregisters.Remove(record); _centralDbContext.SaveChanges(); return Ok(new { message = "Overtime record deleted successfully." }); } #endregion } }