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:
2026-10-01 03:46:03 +05:30
parent 6abef835e8
commit 51f0021e27
16 changed files with 501 additions and 45 deletions
+47 -6
View File
@@ -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);
}
}
}
+7
View File
@@ -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)
{
+24
View File
@@ -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;
}