PSTW_CentralizeSystem/Controllers/API/OvertimeAPI.cs
2025-04-16 17:15:00 +08:00

739 lines
26 KiB
C#

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<OvertimeAPI> _logger;
private readonly CentralSystemContext _centralDbContext;
private readonly UserManager<UserModel> _userManager;
private readonly OvertimePdfService _pdfService;
private readonly IWebHostEnvironment _env;
public OvertimeAPI(ILogger<OvertimeAPI> logger, CentralSystemContext centralDbContext, UserManager<UserModel> 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<IActionResult> UpdateRate([FromBody] List<RateModel> 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<IActionResult> 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<IActionResult> 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<IActionResult> UpdateHoliday([FromBody] List<CalendarModel> 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<IActionResult> 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<IActionResult> 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<IActionResult> UpdateWeekend([FromBody] List<StateModel> 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<IActionResult> 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<IActionResult> 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<IActionResult> SaveOvertimeRecordsWithPdf([FromBody] List<OtRegisterModel> 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<IActionResult> GetOvertimeRecordById(int id)
{
var record = await _centralDbContext.Otregisters.FindAsync(id);
if (record == null)
return NotFound();
return Ok(record);
}
[HttpPost]
[Route("UpdateOvertimeRecord")]
public async Task<IActionResult> 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
}
}