From 9d942d641127f6506edd969b444acfaf35849de2 Mon Sep 17 00:00:00 2001 From: androidlover5842 Date: Wed, 4 Feb 2026 14:58:16 +0530 Subject: [PATCH] createBooking: change checkout date based on property policy while editing checking date --- .../data/api/model/BookingModels.kt | 16 ++++++ .../data/api/service/BookingApi.kt | 8 +++ .../ui/booking/BookingCreateScreen.kt | 39 ++++++++------ .../ui/booking/BookingCreateViewModel.kt | 51 +++++++++++++++++++ 4 files changed, 99 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/android/trisolarispms/data/api/model/BookingModels.kt b/app/src/main/java/com/android/trisolarispms/data/api/model/BookingModels.kt index 2d087ac..39ad902 100644 --- a/app/src/main/java/com/android/trisolarispms/data/api/model/BookingModels.kt +++ b/app/src/main/java/com/android/trisolarispms/data/api/model/BookingModels.kt @@ -112,6 +112,22 @@ data class BookingBillableNightsResponse( val billableNights: Long? = null ) +data class BookingExpectedCheckoutPreviewRequest( + val checkInAt: String, + val billableNights: Int? = null, + val billingMode: BookingBillingMode? = null, + val billingCheckinTime: String? = null, + val billingCheckoutTime: String? = null +) + +data class BookingExpectedCheckoutPreviewResponse( + val expectedCheckOutAt: String? = null, + val billableNights: Int? = null, + val billingMode: BookingBillingMode? = null, + val billingCheckinTime: String? = null, + val billingCheckoutTime: String? = null +) + data class BookingDetailsResponse( val id: String? = null, val status: String? = null, diff --git a/app/src/main/java/com/android/trisolarispms/data/api/service/BookingApi.kt b/app/src/main/java/com/android/trisolarispms/data/api/service/BookingApi.kt index e8ac98d..ab588dd 100644 --- a/app/src/main/java/com/android/trisolarispms/data/api/service/BookingApi.kt +++ b/app/src/main/java/com/android/trisolarispms/data/api/service/BookingApi.kt @@ -13,6 +13,8 @@ import com.android.trisolarispms.data.api.model.BookingListItem import com.android.trisolarispms.data.api.model.BookingBulkCheckInRequest import com.android.trisolarispms.data.api.model.BookingBillableNightsRequest import com.android.trisolarispms.data.api.model.BookingBillableNightsResponse +import com.android.trisolarispms.data.api.model.BookingExpectedCheckoutPreviewRequest +import com.android.trisolarispms.data.api.model.BookingExpectedCheckoutPreviewResponse import com.android.trisolarispms.data.api.model.BookingExpectedDatesRequest import com.android.trisolarispms.data.api.model.BookingDetailsResponse import com.android.trisolarispms.data.api.model.BookingBalanceResponse @@ -69,6 +71,12 @@ interface BookingApi { @Body body: BookingBillableNightsRequest ): Response + @POST("properties/{propertyId}/bookings/expected-checkout-preview") + suspend fun previewExpectedCheckout( + @Path("propertyId") propertyId: String, + @Body body: BookingExpectedCheckoutPreviewRequest + ): Response + @POST("properties/{propertyId}/bookings/{bookingId}/billing-policy") suspend fun updateBookingBillingPolicy( @Path("propertyId") propertyId: String, diff --git a/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateScreen.kt b/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateScreen.kt index 769cd7c..8db46bf 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateScreen.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateScreen.kt @@ -66,24 +66,36 @@ fun BookingCreateScreen( val transportOptions = listOf("CAR", "BIKE", "TRAIN", "PLANE", "BUS", "FOOT", "CYCLE", "OTHER") val billingModeMenuExpanded = remember { mutableStateOf(false) } val displayFormatter = remember { DateTimeFormatter.ofPattern("dd-MM-yy HH:mm") } + val timeFormatter = remember { DateTimeFormatter.ofPattern("HH:mm") } val phoneCountryMenuExpanded = remember { mutableStateOf(false) } val phoneCountries = remember { phoneCountryOptions() } val phoneCountrySearch = remember { mutableStateOf("") } + val applyCheckInSelection: (LocalDate, String) -> Unit = { date, time -> + checkInDate.value = date + checkInTime.value = time + val checkInAt = formatBookingIso(date, time) + viewModel.onExpectedCheckInAtChange(checkInAt) + viewModel.autoSetBillingFromCheckIn(checkInAt) + viewModel.refreshExpectedCheckoutPreview(propertyId) + } + LaunchedEffect(propertyId) { viewModel.reset() viewModel.loadBillingPolicy(propertyId) val now = OffsetDateTime.now() - checkInDate.value = now.toLocalDate() - checkInTime.value = now.format(DateTimeFormatter.ofPattern("HH:mm")) - val nowIso = now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) - viewModel.onExpectedCheckInAtChange(nowIso) - viewModel.autoSetBillingFromCheckIn(nowIso) checkInNow.value = true val defaultCheckoutDate = now.toLocalDate().plusDays(1) checkOutDate.value = defaultCheckoutDate checkOutTime.value = "11:00" viewModel.onExpectedCheckOutAtChange(formatBookingIso(defaultCheckoutDate, checkOutTime.value)) + applyCheckInSelection(now.toLocalDate(), now.format(timeFormatter)) + } + + LaunchedEffect(state.expectedCheckOutAt) { + val parsed = runCatching { OffsetDateTime.parse(state.expectedCheckOutAt) }.getOrNull() ?: return@LaunchedEffect + checkOutDate.value = parsed.toLocalDate() + checkOutTime.value = parsed.format(timeFormatter) } SaveTopBarScaffold( @@ -111,11 +123,7 @@ fun BookingCreateScreen( checkInNow.value = enabled if (enabled) { val now = OffsetDateTime.now() - checkInDate.value = now.toLocalDate() - checkInTime.value = now.format(DateTimeFormatter.ofPattern("HH:mm")) - val nowIso = now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) - viewModel.onExpectedCheckInAtChange(nowIso) - viewModel.autoSetBillingFromCheckIn(nowIso) + applyCheckInSelection(now.toLocalDate(), now.format(timeFormatter)) } else { viewModel.onExpectedCheckInAtChange("") } @@ -181,6 +189,7 @@ fun BookingCreateScreen( onClick = { billingModeMenuExpanded.value = false viewModel.onBillingModeChange(mode) + viewModel.refreshExpectedCheckoutPreview(propertyId) } ) } @@ -191,7 +200,10 @@ fun BookingCreateScreen( BookingTimePickerTextField( value = state.billingCheckoutTime, label = { Text("Billing check-out (HH:mm)") }, - onTimeSelected = viewModel::onBillingCheckoutTimeChange, + onTimeSelected = { selectedTime -> + viewModel.onBillingCheckoutTimeChange(selectedTime) + viewModel.refreshExpectedCheckoutPreview(propertyId) + }, modifier = Modifier.fillMaxWidth() ) } @@ -481,10 +493,7 @@ fun BookingCreateScreen( minDate = LocalDate.now(), onDismiss = { showCheckInPicker.value = false }, onConfirm = { date, time -> - checkInDate.value = date - checkInTime.value = time - val formatted = formatBookingIso(date, time) - viewModel.onExpectedCheckInAtChange(formatted) + applyCheckInSelection(date, time) showCheckInPicker.value = false } ) diff --git a/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateViewModel.kt b/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateViewModel.kt index 0f9a8a0..d401bde 100644 --- a/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateViewModel.kt +++ b/app/src/main/java/com/android/trisolarispms/ui/booking/BookingCreateViewModel.kt @@ -6,6 +6,7 @@ import com.android.trisolarispms.data.api.core.ApiClient import com.android.trisolarispms.data.api.model.BookingBillingMode import com.android.trisolarispms.data.api.model.BookingCreateRequest import com.android.trisolarispms.data.api.model.BookingCreateResponse +import com.android.trisolarispms.data.api.model.BookingExpectedCheckoutPreviewRequest import com.android.trisolarispms.data.api.model.GuestDto import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -14,10 +15,16 @@ import kotlinx.coroutines.launch import java.time.OffsetDateTime class BookingCreateViewModel : ViewModel() { + private companion object { + const val DEFAULT_PREVIEW_BILLABLE_NIGHTS = 1 + } + private val _state = MutableStateFlow(BookingCreateState()) val state: StateFlow = _state + private var expectedCheckoutPreviewRequestId: Long = 0 fun reset() { + expectedCheckoutPreviewRequestId = 0 _state.value = BookingCreateState() } @@ -76,6 +83,50 @@ class BookingCreateViewModel : ViewModel() { _state.update { it.copy(billingCheckoutTime = value, error = null) } } + fun refreshExpectedCheckoutPreview(propertyId: String) { + if (propertyId.isBlank()) return + val requestBody = buildExpectedCheckoutPreviewRequest(_state.value) ?: return + val requestId = ++expectedCheckoutPreviewRequestId + viewModelScope.launch { + try { + val api = ApiClient.create() + val response = api.previewExpectedCheckout( + propertyId = propertyId, + body = requestBody + ) + val expectedCheckOutAt = response.body()?.expectedCheckOutAt?.trim().orEmpty() + if (!response.isSuccessful || expectedCheckOutAt.isBlank() || requestId != expectedCheckoutPreviewRequestId) { + return@launch + } + _state.update { current -> + if (requestId != expectedCheckoutPreviewRequestId) { + current + } else { + current.copy(expectedCheckOutAt = expectedCheckOutAt, error = null) + } + } + } catch (_: Exception) { + // Keep user-entered check-out on preview failures. + } + } + } + + private fun buildExpectedCheckoutPreviewRequest(state: BookingCreateState): BookingExpectedCheckoutPreviewRequest? { + val expectedCheckInAt = state.expectedCheckInAt.trim() + if (expectedCheckInAt.isBlank()) return null + val customBillingCheckoutTime = state.billingCheckoutTime.trim().ifBlank { null } + return BookingExpectedCheckoutPreviewRequest( + checkInAt = expectedCheckInAt, + billableNights = DEFAULT_PREVIEW_BILLABLE_NIGHTS, + billingMode = state.billingMode, + billingCheckoutTime = if (state.billingMode == BookingBillingMode.CUSTOM_WINDOW) { + customBillingCheckoutTime + } else { + null + } + ) + } + fun onPhoneCountryChange(value: String) { val option = findPhoneCountryOption(value) _state.update { current ->