using FluentValidation; using Microsoft.AspNetCore.Mvc; using Meezi.API.Models.Hr; using Meezi.API.Services; using Meezi.Core.Enums; using Meezi.Core.Interfaces; using Meezi.Shared; namespace Meezi.API.Controllers; [Route("api/cafes/{cafeId}")] public class HrController : CafeApiControllerBase { private readonly IHrService _hr; private readonly IValidator _leaveValidator; private readonly IValidator _reviewValidator; private readonly IValidator _salaryValidator; public HrController( IHrService hr, IValidator leaveValidator, IValidator reviewValidator, IValidator salaryValidator) { _hr = hr; _leaveValidator = leaveValidator; _reviewValidator = reviewValidator; _salaryValidator = salaryValidator; } [HttpGet("employees")] public async Task GetEmployees( string cafeId, ITenantContext tenant, [FromQuery] string? branchId = null, CancellationToken ct = default) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var data = await _hr.GetEmployeesAsync(cafeId, branchId, ct); return Ok(new ApiResponse>(true, data)); } [HttpGet("employees/{employeeId}")] public async Task GetEmployee(string cafeId, string employeeId, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var data = await _hr.GetEmployeeAsync(cafeId, employeeId, ct); if (data is null) return NotFoundError(); return Ok(new ApiResponse(true, data)); } [HttpGet("employees/{employeeId}/shift/today")] public async Task GetTodayShift(string cafeId, string employeeId, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsureSelfOrManager(employeeId, tenant) is { } forbidden) return forbidden; var data = await _hr.GetTodayShiftAsync(cafeId, employeeId, ct); if (data is null) return NotFoundError(); return Ok(new ApiResponse(true, data)); } [HttpPost("employees/{employeeId}/attendance/clock-in")] public async Task ClockIn(string cafeId, string employeeId, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsureSelfOrManager(employeeId, tenant) is { } forbidden) return forbidden; var data = await _hr.ClockInAsync(cafeId, employeeId, ct); if (data is null) return NotFoundError(); return Ok(new ApiResponse(true, data)); } [HttpPost("employees/{employeeId}/attendance/clock-out")] public async Task ClockOut(string cafeId, string employeeId, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsureSelfOrManager(employeeId, tenant) is { } forbidden) return forbidden; var data = await _hr.ClockOutAsync(cafeId, employeeId, ct); if (data is null) return BadRequest(new ApiResponse(false, null, new ApiError("INVALID", "Clock-in required before clock-out."))); return Ok(new ApiResponse(true, data)); } [HttpGet("attendance")] public async Task GetAttendance( string cafeId, [FromQuery] string? employeeId, [FromQuery] DateOnly? from, [FromQuery] DateOnly? to, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var data = await _hr.GetAttendanceAsync(cafeId, employeeId, from, to, ct); return Ok(new ApiResponse>(true, data)); } [HttpGet("employees/{employeeId}/shifts")] public async Task GetShifts(string cafeId, string employeeId, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var data = await _hr.GetShiftsAsync(cafeId, employeeId, ct); return Ok(new ApiResponse>(true, data)); } [HttpPut("employees/{employeeId}/shifts")] public async Task UpsertShifts( string cafeId, string employeeId, [FromBody] UpsertShiftsRequest request, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsureManager(tenant) is { } forbidden) return forbidden; var data = await _hr.UpsertShiftsAsync(cafeId, employeeId, request, ct); return Ok(new ApiResponse>(true, data)); } [HttpGet("leave-requests")] public async Task GetLeaveRequests( string cafeId, [FromQuery] LeaveStatus? status, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var data = await _hr.GetLeaveRequestsAsync(cafeId, status, ct); return Ok(new ApiResponse>(true, data)); } [HttpPost("employees/{employeeId}/leave-requests")] public async Task CreateLeave( string cafeId, string employeeId, [FromBody] CreateLeaveRequest request, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsureSelfOrManager(employeeId, tenant) is { } forbidden) return forbidden; var validation = await _leaveValidator.ValidateAsync(request, ct); if (!validation.IsValid) return BadRequest(ValidationError(validation)); var data = await _hr.CreateLeaveRequestAsync(cafeId, employeeId, request, ct); if (data is null) return NotFoundError(); return Ok(new ApiResponse(true, data)); } [HttpPatch("leave-requests/{leaveId}/status")] public async Task ReviewLeave( string cafeId, string leaveId, [FromBody] ReviewLeaveRequest request, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsureManager(tenant) is { } forbidden) return forbidden; var validation = await _reviewValidator.ValidateAsync(request, ct); if (!validation.IsValid) return BadRequest(ValidationError(validation)); var data = await _hr.ReviewLeaveRequestAsync(cafeId, leaveId, tenant.UserId!, request, ct); if (data is null) return NotFoundError(); return Ok(new ApiResponse(true, data)); } [HttpGet("salaries")] public async Task GetSalaries( string cafeId, [FromQuery] string? monthYear, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; var data = await _hr.GetSalariesAsync(cafeId, monthYear, ct); return Ok(new ApiResponse>(true, data)); } [HttpPost("salaries")] public async Task CreateSalary( string cafeId, [FromBody] CreateSalaryRequest request, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsureManager(tenant) is { } forbidden) return forbidden; var validation = await _salaryValidator.ValidateAsync(request, ct); if (!validation.IsValid) return BadRequest(ValidationError(validation)); var data = await _hr.CreateSalaryAsync(cafeId, request, ct); if (data is null) return NotFoundError(); return Ok(new ApiResponse(true, data)); } [HttpPatch("salaries/{salaryId}/paid")] public async Task MarkPaid(string cafeId, string salaryId, ITenantContext tenant, CancellationToken ct) { if (EnsureCafeAccess(cafeId, tenant) is { } denied) return denied; if (EnsureManager(tenant) is { } forbidden) return forbidden; var data = await _hr.MarkSalaryPaidAsync(cafeId, salaryId, ct); if (data is null) return NotFoundError(); return Ok(new ApiResponse(true, data)); } }