improve booking section

This commit is contained in:
androidlover5842
2026-01-29 09:18:35 +05:30
parent 3a7667c609
commit 869e59aaac
6 changed files with 106 additions and 22 deletions

View File

@@ -36,6 +36,12 @@ interface GuestApi {
@Query("vehicleNumber") vehicleNumber: String? = null
): Response<List<GuestDto>>
@GET("properties/{propertyId}/guests/{guestId}")
suspend fun getGuest(
@Path("propertyId") propertyId: String,
@Path("guestId") guestId: String
): Response<GuestDto>
@POST("properties/{propertyId}/guests/{guestId}/vehicles")
suspend fun addGuestVehicle(
@Path("propertyId") propertyId: String,

View File

@@ -12,6 +12,7 @@ data class BookingCreateRequest(
val expectedCheckInAt: String,
val expectedCheckOutAt: String,
val source: String? = null,
val guestPhoneE164: String? = null,
val transportMode: String? = null,
val adultCount: Int? = null,
val totalGuestCount: Int? = null,

View File

@@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope
import com.android.trisolarispms.data.api.ApiClient
import com.android.trisolarispms.data.api.model.BookingCreateRequest
import com.android.trisolarispms.data.api.model.BookingCreateResponse
import com.android.trisolarispms.data.api.model.BookingLinkGuestRequest
import com.android.trisolarispms.data.api.model.GuestDto
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -83,21 +82,13 @@ class BookingCreateViewModel : ViewModel() {
} else {
null
}
val existingGuest = if (!phone.isNullOrBlank()) {
val guestResponse = api.searchGuests(propertyId, phone = phone)
if (guestResponse.isSuccessful) {
guestResponse.body().orEmpty().firstOrNull()
} else {
_state.update { it.copy(isLoading = false, error = "Guest search failed: ${guestResponse.code()}") }
return@launch
}
} else null
val response = api.createBooking(
propertyId = propertyId,
body = BookingCreateRequest(
expectedCheckInAt = checkIn,
expectedCheckOutAt = checkOut,
source = current.source.trim().ifBlank { null },
guestPhoneE164 = phone,
transportMode = current.transportMode.trim().ifBlank { null },
adultCount = adultCount,
totalGuestCount = totalGuestCount,
@@ -106,19 +97,8 @@ class BookingCreateViewModel : ViewModel() {
)
val body = response.body()
if (response.isSuccessful && body != null) {
if (existingGuest?.id != null) {
val linkResponse = api.linkGuest(
propertyId = propertyId,
bookingId = body.id.orEmpty(),
body = BookingLinkGuestRequest(existingGuest.id)
)
if (!linkResponse.isSuccessful) {
_state.update { it.copy(isLoading = false, error = "Link guest failed: ${linkResponse.code()}") }
return@launch
}
}
_state.update { it.copy(isLoading = false, error = null) }
onDone(body, existingGuest, phone)
onDone(body, null, phone)
} else {
_state.update { it.copy(isLoading = false, error = "Create failed: ${response.code()}") }
}

View File

@@ -0,0 +1,56 @@
package com.android.trisolarispms.ui.booking
import com.google.i18n.phonenumbers.PhoneNumberUtil
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType
import java.util.Locale
data class PhoneCountryOption(
val code: String,
val name: String,
val dialCode: String,
val maxLength: Int
)
private val fallbackPhoneCountries: List<PhoneCountryOption> = listOf(
PhoneCountryOption(code = "IN", name = "India", dialCode = "91", maxLength = 10),
PhoneCountryOption(code = "US", name = "United States", dialCode = "1", maxLength = 10)
)
private val phoneCountryOptions: List<PhoneCountryOption> by lazy {
val util = PhoneNumberUtil.getInstance()
val regions = util.supportedRegions
val options = regions.mapNotNull { region ->
val dialCode = util.getCountryCodeForRegion(region)
if (dialCode == 0) return@mapNotNull null
val maxLength = guessMaxLength(util, region) ?: 15
val name = Locale("", region).displayCountry
PhoneCountryOption(
code = region,
name = name,
dialCode = dialCode.toString(),
maxLength = maxLength
)
}.sortedWith(compareBy({ it.name.lowercase() }, { it.dialCode }))
if (options.isNotEmpty()) options else fallbackPhoneCountries
}
fun phoneCountryOptions(): List<PhoneCountryOption> = phoneCountryOptions
fun findPhoneCountryOption(code: String): PhoneCountryOption {
return phoneCountryOptions.firstOrNull { it.code == code }
?: phoneCountryOptions.first()
}
private fun guessMaxLength(util: PhoneNumberUtil, region: String): Int? {
val types = listOf(
PhoneNumberType.MOBILE,
PhoneNumberType.FIXED_LINE,
PhoneNumberType.FIXED_LINE_OR_MOBILE
)
val lengths = types.mapNotNull { type ->
util.getExampleNumberForType(region, type)?.let { example ->
util.getNationalSignificantNumber(example).length
}
}
return lengths.maxOrNull()
}

View File

@@ -44,6 +44,7 @@ fun GuestInfoScreen(
LaunchedEffect(guestId) {
viewModel.reset()
viewModel.setInitial(initialGuest, initialPhone)
viewModel.loadGuest(propertyId, guestId, initialPhone)
}
Scaffold(

View File

@@ -46,6 +46,46 @@ class GuestInfoViewModel : ViewModel() {
}
}
fun loadGuest(propertyId: String, guestId: String, fallbackPhone: String?) {
if (propertyId.isBlank() || guestId.isBlank()) return
viewModelScope.launch {
_state.update { it.copy(isLoading = true, error = null) }
try {
val api = ApiClient.create()
val response = api.getGuest(propertyId = propertyId, guestId = guestId)
val guest = response.body()
if (response.isSuccessful && guest != null) {
_state.update {
it.copy(
phoneE164 = guest.phoneE164 ?: fallbackPhone.orEmpty(),
name = guest.name.orEmpty(),
nationality = guest.nationality.orEmpty(),
addressText = guest.addressText.orEmpty(),
isLoading = false,
error = null
)
}
} else {
_state.update {
it.copy(
phoneE164 = it.phoneE164.ifBlank { fallbackPhone.orEmpty() },
isLoading = false,
error = "Load failed: ${response.code()}"
)
}
}
} catch (e: Exception) {
_state.update {
it.copy(
phoneE164 = it.phoneE164.ifBlank { fallbackPhone.orEmpty() },
isLoading = false,
error = e.localizedMessage ?: "Load failed"
)
}
}
}
}
fun submit(propertyId: String, guestId: String, onDone: () -> Unit) {
if (propertyId.isBlank() || guestId.isBlank()) return
val current = state.value