first commit
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
using Fitness_core_BE.DTOs.TimeBankCard;
|
||||
using Fitness_core_BE.Helpers;
|
||||
using Fitness_core_BE.Infra.UoW;
|
||||
using Fitness_core_BE.Interfaces;
|
||||
using Fitness_core_BE.Interfaces.Services;
|
||||
using Fitness_core_BE.Models;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Fitness_core_BE.Services
|
||||
{
|
||||
public class TimeBankCardService : ITimeBankCardService
|
||||
{
|
||||
private readonly ITimeBankCardRepository _timeBankCardRepository;
|
||||
private readonly IUnitOfWork _uow;
|
||||
private readonly UserRoleHelper _userRoleHelper;
|
||||
|
||||
public TimeBankCardService(ITimeBankCardRepository timeBankCardRepository, IUnitOfWork uow, UserRoleHelper userRoleHelper)
|
||||
{
|
||||
_timeBankCardRepository = timeBankCardRepository;
|
||||
_uow = uow;
|
||||
_userRoleHelper = userRoleHelper;
|
||||
}
|
||||
|
||||
public async Task<object> IssueCard(object payload, HttpContext httpContext)
|
||||
{
|
||||
await _uow.BeginAsync();
|
||||
try
|
||||
{
|
||||
var data = DeserializePayload.Deserialize(payload);
|
||||
var rfidTagId = data["rfidTagId"].GetString() ?? throw new ArgumentException("rfidTagId is required");
|
||||
var totalMinutes = data["totalMinutesPurchased"].GetInt32();
|
||||
if (totalMinutes < 1) throw new ArgumentException("totalMinutesPurchased must be at least 1");
|
||||
|
||||
if (await _timeBankCardRepository.GetByRfidTagId(rfidTagId) != null)
|
||||
throw new ArgumentException("This RFID tag already has an active time-bank card");
|
||||
|
||||
var clientId = await _userRoleHelper.GetClientId(DataExtractor.GetUserIdFromContext(httpContext));
|
||||
|
||||
var card = new TimeBankCard
|
||||
{
|
||||
FKClientId = clientId,
|
||||
RfidTagId = rfidTagId,
|
||||
TotalMinutesPurchased = totalMinutes,
|
||||
MinutesRemaining = totalMinutes,
|
||||
AmountPaid = data.ContainsKey("amountPaid") ? data["amountPaid"].GetDecimal() : 0m,
|
||||
FKCustomerId = data.ContainsKey("customerId") && data["customerId"].ValueKind != JsonValueKind.Null
|
||||
? Guid.Parse(data["customerId"].GetString()!)
|
||||
: null,
|
||||
ExpiresAt = data.ContainsKey("expiresAt") && data["expiresAt"].ValueKind != JsonValueKind.Null
|
||||
? DateTime.Parse(data["expiresAt"].GetString()!).ToUniversalTime()
|
||||
: null
|
||||
};
|
||||
|
||||
var result = await _timeBankCardRepository.CreateTimeBankCard(card);
|
||||
await _uow.CommitAsync();
|
||||
|
||||
return MapToDto(result);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _uow.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<object> Swipe(object payload)
|
||||
{
|
||||
await _uow.BeginAsync();
|
||||
try
|
||||
{
|
||||
var data = DeserializePayload.Deserialize(payload);
|
||||
var rfidTagId = data["rfidTagId"].GetString() ?? throw new ArgumentException("rfidTagId is required");
|
||||
var facilityId = Guid.Parse(data["facilityId"].GetString()!);
|
||||
|
||||
var card = await _timeBankCardRepository.GetByRfidTagId(rfidTagId)
|
||||
?? throw new KeyNotFoundException("No active time-bank card found for this RFID tag");
|
||||
|
||||
if (card.ExpiresAt.HasValue && card.ExpiresAt.Value < DateTime.UtcNow)
|
||||
throw new ArgumentException("This card has expired");
|
||||
|
||||
int? minutesDeducted = null;
|
||||
|
||||
if (!card.ActiveSessionStartedAt.HasValue)
|
||||
{
|
||||
// Tap-in: just open the session. Reject outright if there's no balance left
|
||||
// at all, so a spent card doesn't get to "start the clock" on a visit it
|
||||
// can never pay for.
|
||||
if (card.MinutesRemaining < 1)
|
||||
throw new ArgumentException("This card has no time remaining");
|
||||
|
||||
card.ActiveSessionStartedAt = DateTime.UtcNow;
|
||||
card.ActiveSessionFacilityId = facilityId;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Tap-out: bill for actual elapsed time, rounded up to the next whole minute
|
||||
// (a 61-second visit still costs 2 minutes, not 1) -- capped at whatever
|
||||
// balance remains rather than letting a forgotten tap-out run the balance
|
||||
// negative.
|
||||
var elapsed = DateTime.UtcNow - card.ActiveSessionStartedAt.Value;
|
||||
var elapsedMinutes = Math.Max(1, (int)Math.Ceiling(elapsed.TotalMinutes));
|
||||
minutesDeducted = Math.Min(elapsedMinutes, card.MinutesRemaining);
|
||||
|
||||
card.MinutesRemaining -= minutesDeducted.Value;
|
||||
|
||||
await _timeBankCardRepository.AddUsageLog(new TimeBankCardUsageLog
|
||||
{
|
||||
FKTimeBankCardId = card.TimeBankCardId,
|
||||
FKClientId = card.FKClientId,
|
||||
FKFacilityId = card.ActiveSessionFacilityId ?? facilityId,
|
||||
MinutesDeducted = minutesDeducted.Value,
|
||||
MinutesRemainingAfter = card.MinutesRemaining
|
||||
});
|
||||
|
||||
card.ActiveSessionStartedAt = null;
|
||||
card.ActiveSessionFacilityId = null;
|
||||
}
|
||||
|
||||
await _uow.CommitAsync();
|
||||
return MapToDto(card, minutesDeducted);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _uow.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<object> GetBalance(object payload)
|
||||
{
|
||||
var data = DeserializePayload.Deserialize(payload);
|
||||
var rfidTagId = data["rfidTagId"].GetString() ?? throw new ArgumentException("rfidTagId is required");
|
||||
|
||||
var card = await _timeBankCardRepository.GetByRfidTagId(rfidTagId)
|
||||
?? throw new KeyNotFoundException("No active time-bank card found for this RFID tag");
|
||||
|
||||
return MapToDto(card);
|
||||
}
|
||||
|
||||
public async Task<object> GetMyCards(object payload, HttpContext httpContext)
|
||||
{
|
||||
var cusId = await _userRoleHelper.GetCusId(DataExtractor.GetUserIdFromContext(httpContext));
|
||||
var cards = await _timeBankCardRepository.GetByCustomerId(cusId);
|
||||
return cards.Select(c => MapToDto(c)).ToList();
|
||||
}
|
||||
|
||||
private static TimeBankCardDto MapToDto(TimeBankCard card, int? minutesDeductedThisSwipe = null) => new()
|
||||
{
|
||||
TimeBankCardId = card.TimeBankCardId,
|
||||
RfidTagId = card.RfidTagId,
|
||||
CustomerId = card.FKCustomerId,
|
||||
TotalMinutesPurchased = card.TotalMinutesPurchased,
|
||||
MinutesRemaining = card.MinutesRemaining,
|
||||
AmountPaid = card.AmountPaid,
|
||||
PurchasedAt = card.PurchasedAt,
|
||||
ExpiresAt = card.ExpiresAt,
|
||||
IsActive = card.IsActive,
|
||||
SessionOpen = card.ActiveSessionStartedAt.HasValue,
|
||||
SessionStartedAt = card.ActiveSessionStartedAt,
|
||||
SessionFacilityName = card.ActiveSessionFacility?.Name,
|
||||
MinutesDeductedThisSwipe = minutesDeductedThisSwipe
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user