feat: Enhance booking functionality with invoice management and dashboard summary
- Added customerId to guest booking requests for better identification. - Implemented new endpoints for managing booking invoices and dashboard summaries. - Created BookingInvoice and BookingDashboardSummary DTOs to support new features. - Updated Booking and BookingInvoice models to include payment method and timestamps. - Enhanced BookingService to handle invoice creation upon payment confirmation. - Modified BookingRepository to support CRUD operations for BookingInvoice. - Updated TenantDbContext to include BookingInvoice DbSet and relevant configurations. - Improved error handling and validation for booking completion and payment processes.
This commit is contained in:
@@ -81,12 +81,6 @@ namespace Fitness_core_BE.Repos
|
||||
.Where(b => b.FKClientId == clientId && b.Status == "Confirmed" && b.EndTime < now)
|
||||
.ExecuteUpdateAsync(s => s.SetProperty(b => b.Status, "NoShow"));
|
||||
|
||||
public Task<List<Booking>> GetOverdueCheckedInBookingsAsync(Guid clientId, DateTime now)
|
||||
=> _context.Bookings
|
||||
.Include(b => b.RfidTag)
|
||||
.Where(b => b.FKClientId == clientId && b.Status == "CheckedIn" && b.EndTime < now)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task<bool> TryClaimRfidTagAsync(Guid tagId, DateTime now)
|
||||
{
|
||||
var rows = await _context.RfidTags
|
||||
@@ -96,5 +90,52 @@ namespace Fitness_core_BE.Repos
|
||||
.SetProperty(t => t.UpdatedAt, now));
|
||||
return rows > 0;
|
||||
}
|
||||
|
||||
public async Task<BookingInvoice> CreateBookingInvoice(BookingInvoice invoice)
|
||||
{
|
||||
invoice.BookingInvoiceId = Guid.NewGuid();
|
||||
invoice.IssuedAt = DateTime.UtcNow;
|
||||
|
||||
_context.BookingInvoices.Add(invoice);
|
||||
return invoice;
|
||||
}
|
||||
|
||||
public Task<List<BookingInvoice>> GetBookingInvoices(Guid clientId, DateTime? fromUtc, DateTime? toUtc)
|
||||
{
|
||||
var query = _context.BookingInvoices.Where(i => i.FKClientId == clientId);
|
||||
if (fromUtc.HasValue) query = query.Where(i => i.IssuedAt >= fromUtc.Value);
|
||||
if (toUtc.HasValue) query = query.Where(i => i.IssuedAt <= toUtc.Value);
|
||||
|
||||
return query.OrderByDescending(i => i.IssuedAt).ToListAsync();
|
||||
}
|
||||
|
||||
public Task<BookingInvoice?> GetBookingInvoiceById(Guid bookingInvoiceId)
|
||||
=> _context.BookingInvoices.FirstOrDefaultAsync(i => i.BookingInvoiceId == bookingInvoiceId);
|
||||
|
||||
public async Task<(int bookingsToday, decimal revenueToday, int checkedInNow, int upcomingConfirmed, decimal outstandingUnpaid)>
|
||||
GetDashboardSummaryAsync(Guid clientId, DateTime now)
|
||||
{
|
||||
var todayStart = now.Date;
|
||||
var todayEnd = todayStart.AddDays(1);
|
||||
|
||||
var bookingsToday = await _context.Bookings
|
||||
.CountAsync(b => b.FKClientId == clientId && b.StartTime >= todayStart && b.StartTime < todayEnd);
|
||||
|
||||
var revenueToday = await _context.Bookings
|
||||
.Where(b => b.FKClientId == clientId && b.PaidAt >= todayStart && b.PaidAt < todayEnd)
|
||||
.SumAsync(b => (decimal?)b.AmountCharged) ?? 0m;
|
||||
|
||||
var checkedInNow = await _context.Bookings
|
||||
.CountAsync(b => b.FKClientId == clientId && b.Status == "CheckedIn");
|
||||
|
||||
var upcomingConfirmed = await _context.Bookings
|
||||
.CountAsync(b => b.FKClientId == clientId && b.Status == "Confirmed" && b.StartTime > now);
|
||||
|
||||
var outstandingUnpaid = await _context.Bookings
|
||||
.Where(b => b.FKClientId == clientId && b.PaymentStatus == "Unpaid")
|
||||
.SumAsync(b => (decimal?)b.AmountCharged) ?? 0m;
|
||||
|
||||
return (bookingsToday, revenueToday, checkedInNow, upcomingConfirmed, outstandingUnpaid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,13 @@ namespace Fitness_core_BE.Repos
|
||||
(l.PrimaryPhoneCached == id || l.EmailCached == id));
|
||||
}
|
||||
|
||||
// Staff picking an existing member to link a walk-in booking to (identification only,
|
||||
// see BookingService.CreateGuestBooking/CreateGuestAdhocBooking) -- tenant-scoped by
|
||||
// construction, same as FindActiveLinkByIdentifierAsync above.
|
||||
public Task<TenantCustomerLink?> GetTenantCustomerLinkById(Guid id)
|
||||
=> _context.TenantCustomerLink
|
||||
.FirstOrDefaultAsync(l => l.TenantCustomerLinkId == id && l.IsActiveAtThisClient);
|
||||
|
||||
|
||||
public async Task<Customer?> UpdateCustomer(Guid customerId, Customer updatedCustomer)
|
||||
{
|
||||
|
||||
@@ -82,8 +82,32 @@ namespace Fitness_core_BE.Repos
|
||||
})
|
||||
.ToListAsync(ct);
|
||||
|
||||
// Paid facility bookings (guest/walk-in) -- a plain Booking read, not a
|
||||
// PaymentTransaction, since Booking has no invoice at all (see BookingInvoice, kept
|
||||
// deliberately separate from CustomerInvoice/PaymentTransaction's membership-only
|
||||
// wiring). PaidAt is the recognition date, not BookedAt/CheckedOutAt.
|
||||
var bookingPayments = await _context.Bookings
|
||||
.AsNoTracking()
|
||||
.Where(booking =>
|
||||
booking.FKClientId == clientId &&
|
||||
booking.PaymentStatus == "Paid" &&
|
||||
booking.PaidAt != null &&
|
||||
booking.PaidAt >= fromUtc &&
|
||||
booking.PaidAt < toUtc)
|
||||
.Select(booking => new LedgerEntryDto
|
||||
{
|
||||
EntryType = "Income",
|
||||
Category = "Facility Bookings",
|
||||
Description = booking.GuestName,
|
||||
Amount = booking.AmountCharged,
|
||||
EntryDate = booking.PaidAt!.Value,
|
||||
IsManual = false
|
||||
})
|
||||
.ToListAsync(ct);
|
||||
|
||||
memberPayments.AddRange(platformInvoices);
|
||||
memberPayments.AddRange(staffPayments);
|
||||
memberPayments.AddRange(bookingPayments);
|
||||
return memberPayments;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user