diff --git a/backend/Controllers/EventsController.cs b/backend/Controllers/EventsController.cs new file mode 100644 index 0000000..20c6072 --- /dev/null +++ b/backend/Controllers/EventsController.cs @@ -0,0 +1,242 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using RacePlannerApi.Data; +using RacePlannerApi.DTOs; +using RacePlannerApi.Models; + +namespace RacePlannerApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +[Authorize] +public class EventsController : ControllerBase +{ + private readonly RacePlannerDbContext _context; + + public EventsController(RacePlannerDbContext context) + { + _context = context; + } + + [HttpPost] + [Authorize(Roles = "Organizer")] + public async Task> CreateEvent(CreateEventRequest request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(); + } + + var eventEntity = new Event + { + Name = request.Name, + Description = request.Description, + EventDate = request.EventDate, + Location = request.Location, + Category = request.Category, + Tags = request.Tags ?? new List(), + MaxParticipants = request.MaxParticipants, + OrganizerId = userId.Value, + Status = EventStatus.Draft + }; + + _context.Events.Add(eventEntity); + await _context.SaveChangesAsync(); + + // Load organizer for response + await _context.Entry(eventEntity).Reference(e => e.Organizer).LoadAsync(); + + return CreatedAtAction( + nameof(GetEvent), + new { id = eventEntity.Id }, + MapToEventDto(eventEntity)); + } + + [HttpGet] + [AllowAnonymous] + public async Task>> GetEvents( + [FromQuery] EventFilterRequest? filter = null) + { + var query = _context.Events + .Include(e => e.Organizer) + .Include(e => e.Registrations) + .AsQueryable(); + + // Apply filters + if (filter?.Category != null) + { + query = query.Where(e => e.Category == filter.Category); + } + + if (filter?.Tags != null && filter.Tags.Any()) + { + query = query.Where(e => e.Tags.Any(t => filter.Tags.Contains(t))); + } + + if (filter?.FromDate != null) + { + query = query.Where(e => e.EventDate >= filter.FromDate); + } + + if (filter?.ToDate != null) + { + query = query.Where(e => e.EventDate <= filter.ToDate); + } + + if (filter?.Status != null && Enum.TryParse(filter.Status, out var status)) + { + query = query.Where(e => e.Status == status); + } + + if (filter?.OrganizerId != null) + { + query = query.Where(e => e.OrganizerId == filter.OrganizerId); + } + + // Default to showing published events only for anonymous users + if (!User.Identity?.IsAuthenticated ?? true) + { + query = query.Where(e => e.Status == EventStatus.Published); + } + + var events = await query + .OrderBy(e => e.EventDate) + .ToListAsync(); + + return Ok(events.Select(MapToEventDto)); + } + + [HttpGet("{id}")] + [AllowAnonymous] + public async Task> GetEvent(Guid id) + { + var eventEntity = await _context.Events + .Include(e => e.Organizer) + .Include(e => e.Registrations) + .FirstOrDefaultAsync(e => e.Id == id); + + if (eventEntity == null) + { + return NotFound(); + } + + // Draft events only visible to organizers + if (eventEntity.Status == EventStatus.Draft) + { + var userId = GetCurrentUserId(); + if (userId == null || eventEntity.OrganizerId != userId) + { + return NotFound(); + } + } + + return Ok(MapToEventDto(eventEntity)); + } + + [HttpPut("{id}")] + [Authorize(Roles = "Organizer")] + public async Task> UpdateEvent(Guid id, UpdateEventRequest request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(); + } + + var eventEntity = await _context.Events + .Include(e => e.Organizer) + .FirstOrDefaultAsync(e => e.Id == id); + + if (eventEntity == null) + { + return NotFound(); + } + + // Only organizer can update their event + if (eventEntity.OrganizerId != userId) + { + return Forbid(); + } + + // Update fields + if (request.Name != null) eventEntity.Name = request.Name; + if (request.Description != null) eventEntity.Description = request.Description; + if (request.EventDate != null) eventEntity.EventDate = request.EventDate.Value; + if (request.Location != null) eventEntity.Location = request.Location; + if (request.Status != null) eventEntity.Status = request.Status.Value; + if (request.Category != null) eventEntity.Category = request.Category; + if (request.Tags != null) eventEntity.Tags = request.Tags; + if (request.MaxParticipants != null) eventEntity.MaxParticipants = request.MaxParticipants; + + eventEntity.UpdatedAt = DateTime.UtcNow; + + await _context.SaveChangesAsync(); + + return Ok(MapToEventDto(eventEntity)); + } + + [HttpDelete("{id}")] + [Authorize(Roles = "Organizer")] + public async Task DeleteEvent(Guid id) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(); + } + + var eventEntity = await _context.Events.FindAsync(id); + + if (eventEntity == null) + { + return NotFound(); + } + + if (eventEntity.OrganizerId != userId) + { + return Forbid(); + } + + _context.Events.Remove(eventEntity); + await _context.SaveChangesAsync(); + + return NoContent(); + } + + private Guid? GetCurrentUserId() + { + var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + if (Guid.TryParse(userIdClaim, out var userId)) + { + return userId; + } + return null; + } + + private static EventDto MapToEventDto(Event eventEntity) + { + return new EventDto + { + Id = eventEntity.Id, + Name = eventEntity.Name, + Description = eventEntity.Description, + EventDate = eventEntity.EventDate, + Location = eventEntity.Location, + Status = eventEntity.Status.ToString(), + Category = eventEntity.Category, + Tags = eventEntity.Tags, + MaxParticipants = eventEntity.MaxParticipants, + CurrentRegistrations = eventEntity.Registrations?.Count ?? 0, + CreatedAt = eventEntity.CreatedAt, + UpdatedAt = eventEntity.UpdatedAt, + Organizer = new UserSummaryDto + { + Id = eventEntity.Organizer.Id, + Name = eventEntity.Organizer.Name, + Email = eventEntity.Organizer.Email + } + }; + } +} \ No newline at end of file diff --git a/backend/Controllers/PaymentsController.cs b/backend/Controllers/PaymentsController.cs new file mode 100644 index 0000000..e6e8024 --- /dev/null +++ b/backend/Controllers/PaymentsController.cs @@ -0,0 +1,258 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using RacePlannerApi.Data; +using RacePlannerApi.DTOs; +using RacePlannerApi.Models; + +namespace RacePlannerApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +[Authorize] +public class PaymentsController : ControllerBase +{ + private readonly RacePlannerDbContext _context; + + public PaymentsController(RacePlannerDbContext context) + { + _context = context; + } + + [HttpPost] + [Authorize(Roles = "Organizer")] + public async Task> RecordPayment(CreatePaymentRequest request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(); + } + + // Load registration with event + var registration = await _context.Registrations + .Include(r => r.Event) + .FirstOrDefaultAsync(r => r.Id == request.RegistrationId); + + if (registration == null) + { + return NotFound(new { error = "Registration not found" }); + } + + // Verify user is organizer of the event + if (registration.Event.OrganizerId != userId.Value) + { + return Forbid(); + } + + // Check registration is not cancelled + if (registration.Status == RegistrationStatus.Cancelled) + { + return BadRequest(new { error = "Cannot record payment for cancelled registration" }); + } + + var payment = new Payment + { + RegistrationId = request.RegistrationId, + Amount = request.Amount, + Method = request.Method, + TransactionId = request.TransactionId, + Notes = request.Notes, + PaymentDate = request.PaymentDate ?? DateTime.UtcNow + }; + + _context.Payments.Add(payment); + await _context.SaveChangesAsync(); + + // Update registration status if fully paid (assuming a fixed fee amount would be known) + // For now, we'll just record the payment + + return CreatedAtAction( + nameof(GetPayment), + new { id = payment.Id }, + MapToPaymentDto(payment)); + } + + [HttpGet("{id}")] + [Authorize(Roles = "Organizer")] + public async Task> GetPayment(Guid id) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(); + } + + var payment = await _context.Payments + .Include(p => p.Registration) + .ThenInclude(r => r.Event) + .FirstOrDefaultAsync(p => p.Id == id); + + if (payment == null) + { + return NotFound(); + } + + // Verify user is organizer + if (payment.Registration.Event.OrganizerId != userId.Value) + { + return Forbid(); + } + + return Ok(MapToPaymentDto(payment)); + } + + [HttpGet("registration/{registrationId}")] + [Authorize(Roles = "Organizer")] + public async Task>> GetRegistrationPayments(Guid registrationId) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(); + } + + // Verify organizer has access to this registration + var registration = await _context.Registrations + .Include(r => r.Event) + .FirstOrDefaultAsync(r => r.Id == registrationId); + + if (registration == null) + { + return NotFound(); + } + + if (registration.Event.OrganizerId != userId.Value) + { + return Forbid(); + } + + var payments = await _context.Payments + .Where(p => p.RegistrationId == registrationId) + .OrderByDescending(p => p.PaymentDate) + .ToListAsync(); + + return Ok(payments.Select(MapToPaymentDto)); + } + + [HttpGet("registration/{registrationId}/status")] + [Authorize] + public async Task> GetPaymentStatus(Guid registrationId) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(); + } + + var registration = await _context.Registrations + .Include(r => r.Event) + .Include(r => r.Payments) + .FirstOrDefaultAsync(r => r.Id == registrationId); + + if (registration == null) + { + return NotFound(); + } + + // Participant can view their own, organizer can view any + var isOrganizer = registration.Event.OrganizerId == userId.Value; + + if (registration.ParticipantId != userId.Value && !isOrganizer) + { + return Forbid(); + } + + var totalPaid = registration.Payments.Sum(p => p.Amount); + var status = totalPaid > 0 ? "paid" : "unpaid"; + + return Ok(new + { + RegistrationId = registrationId, + TotalPaid = totalPaid, + PaymentCount = registration.Payments.Count, + Status = status, + Payments = registration.Payments.Select(MapToPaymentDto) + }); + } + + [HttpGet("event/{eventId}/report")] + [Authorize(Roles = "Organizer")] + public async Task> GetPaymentReport(Guid eventId) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(); + } + + var eventEntity = await _context.Events + .FirstOrDefaultAsync(e => e.Id == eventId && e.OrganizerId == userId.Value); + + if (eventEntity == null) + { + return NotFound(); + } + + var registrations = await _context.Registrations + .Include(r => r.Participant) + .Include(r => r.Payments) + .Where(r => r.EventId == eventId && r.Status != RegistrationStatus.Cancelled) + .ToListAsync(); + + var totalCollected = registrations.Sum(r => r.Payments.Sum(p => p.Amount)); + var totalRegistrations = registrations.Count; + var paidRegistrations = registrations.Count(r => r.Payments.Any()); + var unpaidRegistrations = registrations.Count(r => !r.Payments.Any()); + + var report = new PaymentReportDto + { + EventId = eventId, + EventName = eventEntity.Name, + TotalCollected = totalCollected, + TotalPending = 0, // Would require event fee structure + TotalOutstanding = 0, // Would require event fee structure + TotalRegistrations = totalRegistrations, + PaidRegistrations = paidRegistrations, + PartialRegistrations = 0, // Would require partial payment logic + UnpaidRegistrations = unpaidRegistrations, + Registrations = registrations.Select(r => new RegistrationPaymentDto + { + RegistrationId = r.Id, + ParticipantName = r.Participant.Name, + Status = r.Status.ToString(), + TotalPaid = r.Payments.Sum(p => p.Amount), + AmountDue = 0, // Would require event fee structure + PaymentStatus = r.Payments.Any() ? "paid" : "unpaid", + Payments = r.Payments.Select(MapToPaymentDto).ToList() + }).ToList() + }; + + return Ok(report); + } + + private Guid? GetCurrentUserId() + { + var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + if (Guid.TryParse(userIdClaim, out var userId)) + { + return userId; + } + return null; + } + + private static PaymentDto MapToPaymentDto(Payment payment) + { + return new PaymentDto + { + Id = payment.Id, + RegistrationId = payment.RegistrationId, + Amount = payment.Amount, + Method = payment.Method.ToString(), + TransactionId = payment.TransactionId, + Notes = payment.Notes, + PaymentDate = payment.PaymentDate, + CreatedAt = payment.CreatedAt + }; + } +} \ No newline at end of file diff --git a/backend/Controllers/RegistrationsController.cs b/backend/Controllers/RegistrationsController.cs new file mode 100644 index 0000000..4b227a7 --- /dev/null +++ b/backend/Controllers/RegistrationsController.cs @@ -0,0 +1,303 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using RacePlannerApi.Data; +using RacePlannerApi.DTOs; +using RacePlannerApi.Models; + +namespace RacePlannerApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +[Authorize] +public class RegistrationsController : ControllerBase +{ + private readonly RacePlannerDbContext _context; + + public RegistrationsController(RacePlannerDbContext context) + { + _context = context; + } + + [HttpPost] + public async Task> CreateRegistration(CreateRegistrationRequest request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(); + } + + // Check if event exists and is published + var eventEntity = await _context.Events + .FirstOrDefaultAsync(e => e.Id == request.EventId); + + if (eventEntity == null) + { + return NotFound(new { error = "Event not found" }); + } + + if (eventEntity.Status != EventStatus.Published) + { + return BadRequest(new { error = "Event is not open for registration" }); + } + + // Check if already registered + var existingRegistration = await _context.Registrations + .FirstOrDefaultAsync(r => r.EventId == request.EventId && r.ParticipantId == userId.Value); + + if (existingRegistration != null) + { + return Conflict(new { error = "Already registered for this event" }); + } + + // Check if event is full + if (eventEntity.MaxParticipants.HasValue) + { + var registrationCount = await _context.Registrations + .CountAsync(r => r.EventId == request.EventId && r.Status != RegistrationStatus.Cancelled); + + if (registrationCount >= eventEntity.MaxParticipants.Value) + { + return BadRequest(new { error = "Event is full" }); + } + } + + var registration = new Registration + { + EventId = request.EventId, + ParticipantId = userId.Value, + Status = RegistrationStatus.Pending, + Category = request.Category, + EmergencyContact = request.EmergencyContact + }; + + _context.Registrations.Add(registration); + await _context.SaveChangesAsync(); + + // Load related data + await _context.Entry(registration) + .Reference(r => r.Event) + .LoadAsync(); + await _context.Entry(registration) + .Reference(r => r.Participant) + .LoadAsync(); + await _context.Entry(registration) + .Collection(r => r.Payments) + .LoadAsync(); + + return CreatedAtAction( + nameof(GetRegistration), + new { id = registration.Id }, + MapToRegistrationDto(registration)); + } + + [HttpGet("{id}")] + public async Task> GetRegistration(Guid id) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(); + } + + var registration = await _context.Registrations + .Include(r => r.Event) + .Include(r => r.Participant) + .Include(r => r.Payments) + .FirstOrDefaultAsync(r => r.Id == id); + + if (registration == null) + { + return NotFound(); + } + + // Only participant or event organizer can view + var isOrganizer = await _context.Events + .AnyAsync(e => e.Id == registration.EventId && e.OrganizerId == userId.Value); + + if (registration.ParticipantId != userId.Value && !isOrganizer) + { + return Forbid(); + } + + return Ok(MapToRegistrationDto(registration)); + } + + [HttpGet("my-registrations")] + public async Task>> GetMyRegistrations() + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(); + } + + var registrations = await _context.Registrations + .Include(r => r.Event) + .Include(r => r.Participant) + .Include(r => r.Payments) + .Where(r => r.ParticipantId == userId.Value) + .OrderBy(r => r.Event.EventDate) + .ToListAsync(); + + return Ok(registrations.Select(MapToRegistrationDto)); + } + + [HttpGet("event/{eventId}")] + [Authorize(Roles = "Organizer")] + public async Task>> GetEventRegistrations(Guid eventId) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(); + } + + // Verify user is organizer of this event + var eventEntity = await _context.Events + .FirstOrDefaultAsync(e => e.Id == eventId && e.OrganizerId == userId.Value); + + if (eventEntity == null) + { + return NotFound(); + } + + var registrations = await _context.Registrations + .Include(r => r.Event) + .Include(r => r.Participant) + .Include(r => r.Payments) + .Where(r => r.EventId == eventId) + .OrderBy(r => r.CreatedAt) + .ToListAsync(); + + return Ok(registrations.Select(MapToRegistrationDto)); + } + + [HttpPut("{id}")] + public async Task> UpdateRegistration(Guid id, UpdateRegistrationRequest request) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(); + } + + var registration = await _context.Registrations + .Include(r => r.Event) + .FirstOrDefaultAsync(r => r.Id == id); + + if (registration == null) + { + return NotFound(); + } + + // Participants can only update their own registrations + // Organizers can update any registration for their events + var isOrganizer = registration.Event.OrganizerId == userId.Value; + + if (registration.ParticipantId != userId.Value && !isOrganizer) + { + return Forbid(); + } + + // Update fields + if (request.Status.HasValue && isOrganizer) + { + registration.Status = request.Status.Value; + } + + if (request.Category != null) + { + registration.Category = request.Category; + } + + if (request.EmergencyContact != null) + { + registration.EmergencyContact = request.EmergencyContact; + } + + registration.UpdatedAt = DateTime.UtcNow; + + await _context.SaveChangesAsync(); + + // Reload related data + await _context.Entry(registration) + .Reference(r => r.Participant) + .LoadAsync(); + await _context.Entry(registration) + .Collection(r => r.Payments) + .LoadAsync(); + + return Ok(MapToRegistrationDto(registration)); + } + + [HttpPost("{id}/cancel")] + public async Task> CancelRegistration(Guid id) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Unauthorized(); + } + + var registration = await _context.Registrations + .Include(r => r.Event) + .Include(r => r.Participant) + .Include(r => r.Payments) + .FirstOrDefaultAsync(r => r.Id == id); + + if (registration == null) + { + return NotFound(); + } + + // Only participant or event organizer can cancel + var isOrganizer = registration.Event.OrganizerId == userId.Value; + + if (registration.ParticipantId != userId.Value && !isOrganizer) + { + return Forbid(); + } + + registration.Status = RegistrationStatus.Cancelled; + registration.UpdatedAt = DateTime.UtcNow; + + await _context.SaveChangesAsync(); + + return Ok(MapToRegistrationDto(registration)); + } + + private Guid? GetCurrentUserId() + { + var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + if (Guid.TryParse(userIdClaim, out var userId)) + { + return userId; + } + return null; + } + + private static RegistrationDto MapToRegistrationDto(Registration registration) + { + var totalPaid = registration.Payments?.Sum(p => p.Amount) ?? 0; + + return new RegistrationDto + { + Id = registration.Id, + EventId = registration.EventId, + EventName = registration.Event?.Name ?? string.Empty, + EventDate = registration.Event?.EventDate ?? DateTime.MinValue, + ParticipantId = registration.ParticipantId, + ParticipantName = registration.Participant?.Name ?? string.Empty, + ParticipantEmail = registration.Participant?.Email ?? string.Empty, + Status = registration.Status.ToString(), + Category = registration.Category, + EmergencyContact = registration.EmergencyContact, + CreatedAt = registration.CreatedAt, + UpdatedAt = registration.UpdatedAt, + TotalPaid = totalPaid, + AmountDue = 0 // Will be calculated based on event fee if needed + }; + } +} \ No newline at end of file diff --git a/backend/DTOs/EventDtos.cs b/backend/DTOs/EventDtos.cs new file mode 100644 index 0000000..c82b9c6 --- /dev/null +++ b/backend/DTOs/EventDtos.cs @@ -0,0 +1,85 @@ +using System.ComponentModel.DataAnnotations; +using RacePlannerApi.Models; + +namespace RacePlannerApi.DTOs; + +public class CreateEventRequest +{ + [Required] + [MaxLength(200)] + public string Name { get; set; } = string.Empty; + + [MaxLength(2000)] + public string Description { get; set; } = string.Empty; + + [Required] + public DateTime EventDate { get; set; } + + [Required] + [MaxLength(500)] + public string Location { get; set; } = string.Empty; + + [MaxLength(100)] + public string? Category { get; set; } + + public List Tags { get; set; } = new(); + + public int? MaxParticipants { get; set; } +} + +public class UpdateEventRequest +{ + [MaxLength(200)] + public string? Name { get; set; } + + [MaxLength(2000)] + public string? Description { get; set; } + + public DateTime? EventDate { get; set; } + + [MaxLength(500)] + public string? Location { get; set; } + + public EventStatus? Status { get; set; } + + [MaxLength(100)] + public string? Category { get; set; } + + public List? Tags { get; set; } + + public int? MaxParticipants { get; set; } +} + +public class EventDto +{ + public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public DateTime EventDate { get; set; } + public string Location { get; set; } = string.Empty; + public string Status { get; set; } = string.Empty; + public string? Category { get; set; } + public List Tags { get; set; } = new(); + public int? MaxParticipants { get; set; } + public int CurrentRegistrations { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + public UserSummaryDto Organizer { get; set; } = null!; +} + +public class EventFilterRequest +{ + public string? Category { get; set; } + public List? Tags { get; set; } + public DateTime? FromDate { get; set; } + public DateTime? ToDate { get; set; } + public string? Status { get; set; } + public Guid? OrganizerId { get; set; } +} + +public class UserSummaryDto +{ + public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/backend/DTOs/PaymentDtos.cs b/backend/DTOs/PaymentDtos.cs new file mode 100644 index 0000000..d974ca9 --- /dev/null +++ b/backend/DTOs/PaymentDtos.cs @@ -0,0 +1,62 @@ +using System.ComponentModel.DataAnnotations; +using RacePlannerApi.Models; + +namespace RacePlannerApi.DTOs; + +public class CreatePaymentRequest +{ + [Required] + public Guid RegistrationId { get; set; } + + [Required] + [Range(0.01, double.MaxValue)] + public decimal Amount { get; set; } + + [Required] + public PaymentMethod Method { get; set; } + + [MaxLength(100)] + public string? TransactionId { get; set; } + + [MaxLength(500)] + public string? Notes { get; set; } + + public DateTime? PaymentDate { get; set; } +} + +public class PaymentDto +{ + public Guid Id { get; set; } + public Guid RegistrationId { get; set; } + public decimal Amount { get; set; } + public string Method { get; set; } = string.Empty; + public string? TransactionId { get; set; } + public string? Notes { get; set; } + public DateTime PaymentDate { get; set; } + public DateTime CreatedAt { get; set; } +} + +public class PaymentReportDto +{ + public Guid EventId { get; set; } + public string EventName { get; set; } = string.Empty; + public decimal TotalCollected { get; set; } + public decimal TotalPending { get; set; } + public decimal TotalOutstanding { get; set; } + public int TotalRegistrations { get; set; } + public int PaidRegistrations { get; set; } + public int PartialRegistrations { get; set; } + public int UnpaidRegistrations { get; set; } + public List Registrations { get; set; } = new(); +} + +public class RegistrationPaymentDto +{ + public Guid RegistrationId { get; set; } + public string ParticipantName { get; set; } = string.Empty; + public string Status { get; set; } = string.Empty; + public decimal TotalPaid { get; set; } + public decimal AmountDue { get; set; } + public string PaymentStatus { get; set; } = string.Empty; + public List Payments { get; set; } = new(); +} \ No newline at end of file diff --git a/backend/DTOs/RegistrationDtos.cs b/backend/DTOs/RegistrationDtos.cs new file mode 100644 index 0000000..a75fd16 --- /dev/null +++ b/backend/DTOs/RegistrationDtos.cs @@ -0,0 +1,45 @@ +using System.ComponentModel.DataAnnotations; +using RacePlannerApi.Models; + +namespace RacePlannerApi.DTOs; + +public class CreateRegistrationRequest +{ + [Required] + public Guid EventId { get; set; } + + [MaxLength(100)] + public string? Category { get; set; } + + [MaxLength(200)] + public string? EmergencyContact { get; set; } +} + +public class UpdateRegistrationRequest +{ + public RegistrationStatus? Status { get; set; } + + [MaxLength(100)] + public string? Category { get; set; } + + [MaxLength(200)] + public string? EmergencyContact { get; set; } +} + +public class RegistrationDto +{ + public Guid Id { get; set; } + public Guid EventId { get; set; } + public string EventName { get; set; } = string.Empty; + public DateTime EventDate { get; set; } + public Guid ParticipantId { get; set; } + public string ParticipantName { get; set; } = string.Empty; + public string ParticipantEmail { get; set; } = string.Empty; + public string Status { get; set; } = string.Empty; + public string? Category { get; set; } + public string? EmergencyContact { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public decimal TotalPaid { get; set; } + public decimal AmountDue { get; set; } +} \ No newline at end of file