Refactor CustomerPlanService to use FacilityRepository, remove PlaceService, and introduce RfidTagService
- Updated CustomerPlanService to replace references to PlaceRepo with FacilityRepository for managing customer plans and associated facilities. - Removed PlaceService and its related methods, consolidating functionality into FacilityService. - Added RfidTagService for managing RFID tags, including methods for creating, updating, and setting statuses of tags. - Updated TenantDbContext to include new DbSet for RfidTags and CustomerPlanFacilities, and adjusted model configurations accordingly. - Enhanced FacilityService to support new booking modes and validation for facility creation and updates.
This commit is contained in:
+162
-22
@@ -10,7 +10,7 @@ namespace Fitness_core_BE.Services
|
||||
{
|
||||
public class FacilityService : IFacilityService
|
||||
{
|
||||
private static readonly string[] ValidFacilityTypes = { "Gym", "Pool", "Court", "Class" };
|
||||
private static readonly string[] ValidBookingModes = ["Scheduled", "AdHoc"];
|
||||
|
||||
private readonly IFacilityRepository _facilityRepository;
|
||||
private readonly IUnitOfWork _uow;
|
||||
@@ -31,7 +31,9 @@ namespace Fitness_core_BE.Services
|
||||
var clientId = await _userRoleHelper.GetClientId(DataExtractor.GetUserIdFromContext(httpContext));
|
||||
var facilities = await _facilityRepository.GetAllFacilities(clientId, includeInactive);
|
||||
|
||||
return facilities.Select(MapToDto).ToList();
|
||||
var devicesByFacility = await _facilityRepository.GetDevicesByFacilityIdsAsync(facilities.Select(f => f.FacilityId).ToList());
|
||||
|
||||
return facilities.Select(f => MapToDto(f, devicesByFacility.GetValueOrDefault(f.FacilityId))).ToList();
|
||||
}
|
||||
|
||||
public async Task<object> CreateFacility(object payload, HttpContext httpContext)
|
||||
@@ -41,11 +43,22 @@ namespace Fitness_core_BE.Services
|
||||
{
|
||||
var data = DeserializePayload.Deserialize(payload);
|
||||
if (!data.ContainsKey("name")) throw new KeyNotFoundException("name is required");
|
||||
if (!data.ContainsKey("facilityType")) throw new KeyNotFoundException("facilityType is required");
|
||||
|
||||
var facilityType = data["facilityType"].GetString() ?? "";
|
||||
if (!ValidFacilityTypes.Contains(facilityType))
|
||||
throw new ArgumentException($"facilityType must be one of: {string.Join(", ", ValidFacilityTypes)}");
|
||||
var bookingMode = data.ContainsKey("bookingMode") ? data["bookingMode"].GetString() ?? "Scheduled" : "Scheduled";
|
||||
if (!ValidBookingModes.Contains(bookingMode))
|
||||
throw new ArgumentException($"bookingMode must be one of: {string.Join(", ", ValidBookingModes)}");
|
||||
|
||||
var defaultCapacity = data.ContainsKey("defaultCapacity") ? data["defaultCapacity"].GetInt32() : 1;
|
||||
if (bookingMode == "Scheduled" && defaultCapacity < 1)
|
||||
throw new ArgumentException("defaultCapacity must be at least 1");
|
||||
|
||||
var numberOfUnits = data.ContainsKey("numberOfUnits") ? data["numberOfUnits"].GetInt32() : 1;
|
||||
if (bookingMode == "AdHoc" && numberOfUnits < 1)
|
||||
throw new ArgumentException("numberOfUnits must be at least 1");
|
||||
|
||||
var pricePerHour = data.ContainsKey("pricePerHour") ? data["pricePerHour"].GetDecimal() : 0m;
|
||||
if (pricePerHour < 0)
|
||||
throw new ArgumentException("pricePerHour cannot be negative");
|
||||
|
||||
var clientId = await _userRoleHelper.GetClientId(DataExtractor.GetUserIdFromContext(httpContext));
|
||||
|
||||
@@ -53,18 +66,17 @@ namespace Fitness_core_BE.Services
|
||||
{
|
||||
FKClientId = clientId,
|
||||
Name = data["name"].GetString() ?? "",
|
||||
FacilityType = facilityType,
|
||||
BookingMode = bookingMode,
|
||||
Description = data.ContainsKey("description") ? data["description"].GetString() : null,
|
||||
DefaultCapacity = data.ContainsKey("defaultCapacity") ? data["defaultCapacity"].GetInt32() : 1
|
||||
DefaultCapacity = defaultCapacity,
|
||||
NumberOfUnits = numberOfUnits,
|
||||
PricePerHour = pricePerHour
|
||||
};
|
||||
|
||||
if (facility.DefaultCapacity < 1)
|
||||
throw new ArgumentException("defaultCapacity must be at least 1");
|
||||
|
||||
var result = await _facilityRepository.CreateFacility(facility);
|
||||
await _uow.CommitAsync();
|
||||
|
||||
return MapToDto(result);
|
||||
return MapToDto(result, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -86,12 +98,12 @@ namespace Fitness_core_BE.Services
|
||||
?? throw new KeyNotFoundException("Facility not found");
|
||||
|
||||
if (data.ContainsKey("name")) existing.Name = data["name"].GetString() ?? existing.Name;
|
||||
if (data.ContainsKey("facilityType"))
|
||||
if (data.ContainsKey("bookingMode"))
|
||||
{
|
||||
var facilityType = data["facilityType"].GetString() ?? existing.FacilityType;
|
||||
if (!ValidFacilityTypes.Contains(facilityType))
|
||||
throw new ArgumentException($"facilityType must be one of: {string.Join(", ", ValidFacilityTypes)}");
|
||||
existing.FacilityType = facilityType;
|
||||
var bookingMode = data["bookingMode"].GetString() ?? existing.BookingMode;
|
||||
if (!ValidBookingModes.Contains(bookingMode))
|
||||
throw new ArgumentException($"bookingMode must be one of: {string.Join(", ", ValidBookingModes)}");
|
||||
existing.BookingMode = bookingMode;
|
||||
}
|
||||
if (data.ContainsKey("description")) existing.Description = data["description"].GetString();
|
||||
if (data.ContainsKey("defaultCapacity"))
|
||||
@@ -100,12 +112,25 @@ namespace Fitness_core_BE.Services
|
||||
if (capacity < 1) throw new ArgumentException("defaultCapacity must be at least 1");
|
||||
existing.DefaultCapacity = capacity;
|
||||
}
|
||||
if (data.ContainsKey("numberOfUnits"))
|
||||
{
|
||||
var units = data["numberOfUnits"].GetInt32();
|
||||
if (units < 1) throw new ArgumentException("numberOfUnits must be at least 1");
|
||||
existing.NumberOfUnits = units;
|
||||
}
|
||||
if (data.ContainsKey("pricePerHour"))
|
||||
{
|
||||
var price = data["pricePerHour"].GetDecimal();
|
||||
if (price < 0) throw new ArgumentException("pricePerHour cannot be negative");
|
||||
existing.PricePerHour = price;
|
||||
}
|
||||
if (data.ContainsKey("isActive")) existing.IsActive = data["isActive"].GetBoolean();
|
||||
|
||||
var result = await _facilityRepository.UpdateFacility(existing);
|
||||
await _uow.CommitAsync();
|
||||
|
||||
return MapToDto(result);
|
||||
var devicesByFacility = await _facilityRepository.GetDevicesByFacilityIdsAsync(new List<Guid> { result.FacilityId });
|
||||
return MapToDto(result, devicesByFacility.GetValueOrDefault(result.FacilityId));
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -128,7 +153,7 @@ namespace Fitness_core_BE.Services
|
||||
?? throw new KeyNotFoundException("Facility not found");
|
||||
|
||||
await _uow.CommitAsync();
|
||||
return MapToDto(facility);
|
||||
return MapToDto(facility, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -154,6 +179,9 @@ namespace Fitness_core_BE.Services
|
||||
if (facility == null || facility.FKClientId != clientId)
|
||||
throw new KeyNotFoundException("Facility not found");
|
||||
|
||||
if (facility.BookingMode != "Scheduled")
|
||||
throw new ArgumentException("Sessions can only be created for Scheduled-mode facilities");
|
||||
|
||||
var startTime = DateTime.Parse(data["startTime"].GetString()!).ToUniversalTime();
|
||||
var endTime = DateTime.Parse(data["endTime"].GetString()!).ToUniversalTime();
|
||||
if (endTime <= startTime)
|
||||
@@ -236,15 +264,127 @@ namespace Fitness_core_BE.Services
|
||||
}
|
||||
}
|
||||
|
||||
private static FacilityDto MapToDto(Facility facility) => new()
|
||||
// --- Device binding (absorbed from PlaceService.AssignDevice, unchanged logic) ---
|
||||
|
||||
public async Task<object> AssignDevice(object payload, HttpContext httpContext)
|
||||
{
|
||||
await _uow.BeginAsync();
|
||||
try
|
||||
{
|
||||
var data = DeserializePayload.Deserialize(payload);
|
||||
if (!data.ContainsKey("facilityId")) throw new KeyNotFoundException("facilityId is required");
|
||||
|
||||
var facilityId = Guid.Parse(data["facilityId"].GetString()!);
|
||||
var clientId = await _userRoleHelper.GetClientId(DataExtractor.GetUserIdFromContext(httpContext));
|
||||
|
||||
var facility = await _facilityRepository.GetFacilityById(facilityId);
|
||||
if (facility == null || facility.FKClientId != clientId)
|
||||
throw new KeyNotFoundException("Facility not found");
|
||||
|
||||
var hasDeviceId = data.ContainsKey("deviceId") && data["deviceId"].ValueKind != JsonValueKind.Null;
|
||||
if (!hasDeviceId)
|
||||
{
|
||||
await _facilityRepository.UnassignDeviceAsync(facilityId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var deviceId = Guid.Parse(data["deviceId"].GetString()!);
|
||||
var device = await _facilityRepository.GetDeviceByIdForClientAsync(deviceId, clientId)
|
||||
?? throw new KeyNotFoundException("Device not found");
|
||||
|
||||
if (await _facilityRepository.IsDeviceAssignedToAnotherFacilityAsync(deviceId, facilityId))
|
||||
throw new ArgumentException("This device is already assigned to another facility.");
|
||||
|
||||
await _facilityRepository.AssignDeviceAsync(facilityId, deviceId);
|
||||
}
|
||||
|
||||
await _uow.CommitAsync();
|
||||
|
||||
var devicesByFacility = await _facilityRepository.GetDevicesByFacilityIdsAsync(new List<Guid> { facilityId });
|
||||
return MapToDto(facility, devicesByFacility.GetValueOrDefault(facilityId));
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _uow.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Availability (new) ---
|
||||
|
||||
public async Task<object> GetAvailability(object payload, HttpContext httpContext)
|
||||
{
|
||||
var data = DeserializePayload.Deserialize(payload);
|
||||
if (!data.ContainsKey("startTime")) throw new KeyNotFoundException("startTime is required");
|
||||
if (!data.ContainsKey("durationMinutes")) throw new KeyNotFoundException("durationMinutes is required");
|
||||
|
||||
var startTime = DateTime.Parse(data["startTime"].GetString()!).ToUniversalTime();
|
||||
var durationMinutes = data["durationMinutes"].GetInt32();
|
||||
if (durationMinutes < 1) throw new ArgumentException("durationMinutes must be at least 1");
|
||||
var endTime = startTime.AddMinutes(durationMinutes);
|
||||
|
||||
Guid? facilityId = data.ContainsKey("facilityId") && data["facilityId"].ValueKind != JsonValueKind.Null
|
||||
? Guid.Parse(data["facilityId"].GetString()!)
|
||||
: null;
|
||||
|
||||
var clientId = await _userRoleHelper.GetClientId(DataExtractor.GetUserIdFromContext(httpContext));
|
||||
var facilities = facilityId.HasValue
|
||||
? new List<Facility> { await _facilityRepository.GetFacilityById(facilityId.Value) is { } f && f.FKClientId == clientId ? f : throw new KeyNotFoundException("Facility not found") }
|
||||
: await _facilityRepository.GetAllFacilities(clientId, includeInactive: false);
|
||||
|
||||
var result = new List<FacilityAvailabilityDto>();
|
||||
foreach (var facility in facilities)
|
||||
{
|
||||
if (facility.BookingMode == "AdHoc")
|
||||
{
|
||||
var overlapping = await _facilityRepository.GetOverlappingAdhocBookingCountAsync(facility.FacilityId, startTime, endTime);
|
||||
result.Add(new FacilityAvailabilityDto
|
||||
{
|
||||
FacilityId = facility.FacilityId,
|
||||
FacilityName = facility.Name,
|
||||
BookingMode = facility.BookingMode,
|
||||
TotalUnits = facility.NumberOfUnits,
|
||||
UnitsFree = Math.Max(0, facility.NumberOfUnits - overlapping),
|
||||
PricePerHour = facility.PricePerHour
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var sessions = await _facilityRepository.GetSessions(clientId, facility.FacilityId, startTime, endTime);
|
||||
var overlappingSessions = sessions.Where(s => s.StartTime < endTime && s.EndTime > startTime).ToList();
|
||||
var totalCapacity = overlappingSessions.Sum(s => s.Capacity ?? facility.DefaultCapacity);
|
||||
var totalBooked = 0;
|
||||
foreach (var s in overlappingSessions)
|
||||
totalBooked += await _facilityRepository.GetActiveBookingCount(s.FacilitySessionId);
|
||||
|
||||
result.Add(new FacilityAvailabilityDto
|
||||
{
|
||||
FacilityId = facility.FacilityId,
|
||||
FacilityName = facility.Name,
|
||||
BookingMode = facility.BookingMode,
|
||||
TotalUnits = totalCapacity,
|
||||
UnitsFree = Math.Max(0, totalCapacity - totalBooked),
|
||||
PricePerHour = facility.PricePerHour
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static FacilityDto MapToDto(Facility facility, AccessDevice? device) => new()
|
||||
{
|
||||
FacilityId = facility.FacilityId,
|
||||
Name = facility.Name,
|
||||
FacilityType = facility.FacilityType,
|
||||
BookingMode = facility.BookingMode,
|
||||
Description = facility.Description,
|
||||
DefaultCapacity = facility.DefaultCapacity,
|
||||
NumberOfUnits = facility.NumberOfUnits,
|
||||
PricePerHour = facility.PricePerHour,
|
||||
IsActive = facility.IsActive,
|
||||
CreatedAt = facility.CreatedAt
|
||||
CreatedAt = facility.CreatedAt,
|
||||
DeviceId = device?.DeviceId,
|
||||
DeviceName = device?.DeviceName
|
||||
};
|
||||
|
||||
private static FacilitySessionDto MapToDto(FacilitySession session, string? facilityName = null, int bookedCount = 0) => new()
|
||||
|
||||
Reference in New Issue
Block a user