using Baya.Domain.Entities.Booking; using Baya.Domain.Entities.Catalog; using Baya.Domain.Entities.Identity; using Baya.Infrastructure.Persistence.ValueConversion; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace Baya.Infrastructure.Persistence.Configuration.BookingConfig; internal sealed class BookingConfig : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { // The three-amount split is a DB CHECK (not handler-only): gross = commission + payout, all ≥ 0. builder.ToTable("Bookings", "booking", t => t.HasCheckConstraint( "CK_Bookings_AmountSplit", "[GrossPriceIrr] = [BalinyaarCommissionIrr] + [NursePayoutAmount] " + "AND [GrossPriceIrr] >= 0 AND [BalinyaarCommissionIrr] >= 0 AND [NursePayoutAmount] >= 0")); // Snapshots freeze history. AddressSnapshotJson is encrypted at rest (converter wired in // ApplicationDbContext); both are nvarchar(max) so no length cap. builder.Property(b => b.VariantSnapshotJson).IsRequired(); builder.Property(b => b.AddressSnapshotJson).IsRequired(); builder.Property(b => b.PlatformFeeRate).HasPrecision(5, 4); builder.Property(b => b.CancellationRefundPercentage).HasPrecision(5, 2); builder.Property(b => b.Status).HasMaxLength(30).IsRequired(); builder.Property(b => b.CancellationReason).HasMaxLength(500); builder.Property(b => b.CancelledBy).HasMaxLength(20); builder.Property(b => b.CancellationPolicyCode).HasMaxLength(50); // datetime2 loses Kind on read (comes back Unspecified); re-tag as Utc so JSON serialization keeps // the trailing Z and clients don't misparse these timeline instants as local time // (mvp/blocker-phases/04's follow-up, folded in here since this phase already touches Booking timezone // handling). builder.Property(b => b.ConfirmedAt).HasConversion(new UtcDateTimeConverter()); builder.Property(b => b.CancelledAt).HasConversion(new UtcDateTimeConverter()); builder.Property(b => b.CompletedAt).HasConversion(new UtcDateTimeConverter()); builder.Property(b => b.DisputeWindowEndsAt).HasConversion(new UtcDateTimeConverter()); builder.HasIndex(b => new { b.CustomerId, b.Status }); builder.HasIndex(b => new { b.NurseId, b.Status }); // b13 selects payout-eligible bookings through the dispute-window close. builder.HasIndex(b => b.DisputeWindowEndsAt); // 1:1 with the request that created it (UNIQUE index created automatically). No navigation either // side — the request is read by id at conversion time. builder.HasOne() .WithOne() .HasForeignKey(b => b.BookingRequestId) .IsRequired(); // Denormalized FKs for query performance (no navigations on Booking). partner_center_id is left // FK-less and nullable — partner_centers is DEFERRED to b15. builder.HasOne().WithMany().HasForeignKey(b => b.CustomerId).IsRequired(); builder.HasOne().WithMany().HasForeignKey(b => b.NurseId).IsRequired(); builder.HasOne().WithMany().HasForeignKey(b => b.PatientId).IsRequired(); builder.HasOne().WithMany().HasForeignKey(b => b.VariantId).IsRequired(); builder.HasOne().WithMany().HasForeignKey(b => b.CustomerAddressId).IsRequired(); builder.HasMany(b => b.Sessions).WithOne(s => s.Booking).HasForeignKey(s => s.BookingId); builder.HasQueryFilter(b => b.DeletedAt == null); } }