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; 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; public OvertimeAPI(ILogger logger, CentralSystemContext centralDbContext, UserManager userManager, OvertimePdfService pdfService, IWebHostEnvironment env) { _logger = logger; _centralDbContext = centralDbContext; _userManager = userManager; _pdfService = pdfService; _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 updateDates = new { rateUpdateDate = latestRateUpdate.HasValue ? latestRateUpdate.Value.ToString("dd MMMM yyyy") : null, calendarUpdateDate = latestCalendarUpdate.HasValue ? latestCalendarUpdate.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 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."); 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, }; _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."); } } #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(); // 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"); if (System.IO.File.Exists(logoPath)) logoImage = System.IO.File.ReadAllBytes(logoPath); var stream = _pdfService.GenerateOvertimeTablePdf(mergedRecords, departmentId, fullName, departmentName, logoImage); return File(stream, "application/pdf", $"OvertimeRecords_{year}_{month}.pdf"); } #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 OtStatus #endregion } }