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([FromForm] OvertimeRequestDto request) { _logger.LogInformation("AddOvertimeAsync called (with file upload)."); 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 TimeSpan? officeFrom = TimeSpan.TryParse(request.OfficeFrom, out var of) ? of : (TimeSpan?)null; TimeSpan? officeTo = TimeSpan.TryParse(request.OfficeTo, out var ot) ? ot : (TimeSpan?)null; TimeSpan? outsideFrom = TimeSpan.TryParse(request.OutsideFrom, out var ofr) ? ofr : (TimeSpan?)null; TimeSpan? outsideTo = TimeSpan.TryParse(request.OutsideTo, out var otr) ? otr : (TimeSpan?)null; if ((officeFrom != null && officeTo == null) || (officeFrom == null && officeTo != null)) { return BadRequest("Both Office From and To must be filled if one is provided."); } if ((outsideFrom != null && outsideTo == null) || (outsideFrom == null && outsideTo != null)) { return BadRequest("Both Outside From and To must be filled if one is provided."); } // Save file string pdfPath = null; if (request.File != null && request.File.Length > 0) { var uploadsFolder = Path.Combine(_env.WebRootPath, "Media", "Overtime"); if (!Directory.Exists(uploadsFolder)) Directory.CreateDirectory(uploadsFolder); var fileName = $"OT_{Guid.NewGuid()}{Path.GetExtension(request.File.FileName)}"; var filePath = Path.Combine(uploadsFolder, fileName); using (var stream = new FileStream(filePath, FileMode.Create)) { await request.File.CopyToAsync(stream); } // This is the relative public URL path pdfPath = $"/media/overtime/{fileName}"; } // Map to DB model var newRecord = new OtRegisterModel { OtDate = request.OtDate, OfficeFrom = officeFrom, OfficeTo = officeTo, OfficeBreak = request.OfficeBreak, OutsideFrom = outsideFrom, OutsideTo = outsideTo, OutsideBreak = request.OutsideBreak, StationId = request.StationId, OtDescription = request.OtDescription, OtDays = request.OtDays, UserId = request.UserId, FilePath = pdfPath }; _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) .Select(o => new { o.OvertimeId, o.OtDate, o.OfficeFrom, o.OfficeTo, o.OfficeBreak, o.OutsideFrom, o.OutsideTo, o.OutsideBreak, o.StationId, StationName = o.Stations != null ? o.Stations.StationName : "N/A", o.OtDescription, o.OtDays, o.FilePath, o.UserId }) .OrderByDescending(o => o.OtDate) .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."); } // 1. Delete the file from the server if (!string.IsNullOrEmpty(record.FilePath)) { var filePath = Path.Combine(_env.WebRootPath, record.FilePath.TrimStart('/')); // Construct full path if (System.IO.File.Exists(filePath)) { System.IO.File.Delete(filePath); _logger.LogInformation("File deleted successfully: {FilePath}", filePath); } else { _logger.LogWarning("File not found, could not delete: {FilePath}", filePath); } } // 2. Delete the record from the database _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."); } } [HttpPost("SaveOvertimeRecordsWithPdf")] public async Task SaveOvertimeRecordsWithPdf([FromBody] List records) { if (!ModelState.IsValid) { return BadRequest(ModelState); } try { _logger.LogInformation("SaveOvertimeRecordsWithPdf called with {RecordCount} records", records.Count); foreach (var record in records) { _logger.LogDebug("Processing record with OvertimeId: {OvertimeId}", record.OvertimeId); var existingRecord = await _centralDbContext.Otregisters.FindAsync(record.OvertimeId); if (existingRecord != null) { _logger.LogDebug("Updating existing record with OvertimeId: {OvertimeId}", record.OvertimeId); existingRecord.FilePath = record.FilePath; _centralDbContext.Otregisters.Update(existingRecord); } else { _logger.LogWarning("Record with OvertimeId: {OvertimeId} not found, adding new", record.OvertimeId); _centralDbContext.Otregisters.Add(record); } } await _centralDbContext.SaveChangesAsync(); _logger.LogInformation("Successfully saved {RecordCount} overtime records with PDFs", records.Count); return Ok("Overtime records updated with PDFs."); } catch (Exception ex) { _logger.LogError(ex, "Error saving overtime records with PDFs"); return StatusCode(500, "An error occurred while saving records."); } } [HttpGet("GenerateOvertimePdf")] public IActionResult GenerateOvertimePdf(int month, int year) { var userIdString = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(userIdString) || !int.TryParse(userIdString, out int userId)) { return Unauthorized(); } // Get user and department info var user = _centralDbContext.Users .Include(u => u.Department) .FirstOrDefault(u => u.Id == userId); if (user == null) return NotFound("User not found"); var userFullName = $"{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(); 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(records, departmentId, userFullName, 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 async Task UpdateOvertimeRecord([FromForm] OtRegisterModel model, IFormFile? newFile) { _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); string? oldFilePath = existing.FilePath; // Store the old file path string? newPdfPath = null; // Initialize the new file path // Handle new file upload if (newFile != null && newFile.Length > 0) { var fileName = $"OT_{Guid.NewGuid()}{Path.GetExtension(newFile.FileName)}"; var savePath = Path.Combine(_env.WebRootPath, "media", "Overtime", fileName); newPdfPath = $"/media/Overtime/{fileName}"; using (var stream = new FileStream(savePath, FileMode.Create)) { await newFile.CopyToAsync(stream); } existing.FilePath = newPdfPath; // Update the file path in the database } // Update other fields existing.OtDate = model.OtDate; existing.OfficeFrom = model.OfficeFrom; existing.OfficeTo = model.OfficeTo; existing.OfficeBreak = model.OfficeBreak; existing.OutsideFrom = model.OutsideFrom; existing.OutsideTo = model.OutsideTo; existing.OutsideBreak = model.OutsideBreak; 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); // Delete the old file after saving the new one (if a new file was uploaded) if (newFile != null && !string.IsNullOrEmpty(oldFilePath)) { var fullOldFilePath = Path.Combine(_env.WebRootPath, oldFilePath.TrimStart('/')); if (System.IO.File.Exists(fullOldFilePath)) { System.IO.File.Delete(fullOldFilePath); _logger.LogInformation("Old file deleted successfully: {OldFilePath}", fullOldFilePath); } else { _logger.LogWarning("Old file not found, could not delete: {OldFilePath}", fullOldFilePath); } } 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 } }