improve booking section
This commit is contained in:
@@ -36,6 +36,12 @@ interface GuestApi {
|
|||||||
@Query("vehicleNumber") vehicleNumber: String? = null
|
@Query("vehicleNumber") vehicleNumber: String? = null
|
||||||
): Response<List<GuestDto>>
|
): 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")
|
@POST("properties/{propertyId}/guests/{guestId}/vehicles")
|
||||||
suspend fun addGuestVehicle(
|
suspend fun addGuestVehicle(
|
||||||
@Path("propertyId") propertyId: String,
|
@Path("propertyId") propertyId: String,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ data class BookingCreateRequest(
|
|||||||
val expectedCheckInAt: String,
|
val expectedCheckInAt: String,
|
||||||
val expectedCheckOutAt: String,
|
val expectedCheckOutAt: String,
|
||||||
val source: String? = null,
|
val source: String? = null,
|
||||||
|
val guestPhoneE164: String? = null,
|
||||||
val transportMode: String? = null,
|
val transportMode: String? = null,
|
||||||
val adultCount: Int? = null,
|
val adultCount: Int? = null,
|
||||||
val totalGuestCount: Int? = null,
|
val totalGuestCount: Int? = null,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import com.android.trisolarispms.data.api.ApiClient
|
import com.android.trisolarispms.data.api.ApiClient
|
||||||
import com.android.trisolarispms.data.api.model.BookingCreateRequest
|
import com.android.trisolarispms.data.api.model.BookingCreateRequest
|
||||||
import com.android.trisolarispms.data.api.model.BookingCreateResponse
|
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 com.android.trisolarispms.data.api.model.GuestDto
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@@ -83,21 +82,13 @@ class BookingCreateViewModel : ViewModel() {
|
|||||||
} else {
|
} else {
|
||||||
null
|
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(
|
val response = api.createBooking(
|
||||||
propertyId = propertyId,
|
propertyId = propertyId,
|
||||||
body = BookingCreateRequest(
|
body = BookingCreateRequest(
|
||||||
expectedCheckInAt = checkIn,
|
expectedCheckInAt = checkIn,
|
||||||
expectedCheckOutAt = checkOut,
|
expectedCheckOutAt = checkOut,
|
||||||
source = current.source.trim().ifBlank { null },
|
source = current.source.trim().ifBlank { null },
|
||||||
|
guestPhoneE164 = phone,
|
||||||
transportMode = current.transportMode.trim().ifBlank { null },
|
transportMode = current.transportMode.trim().ifBlank { null },
|
||||||
adultCount = adultCount,
|
adultCount = adultCount,
|
||||||
totalGuestCount = totalGuestCount,
|
totalGuestCount = totalGuestCount,
|
||||||
@@ -106,19 +97,8 @@ class BookingCreateViewModel : ViewModel() {
|
|||||||
)
|
)
|
||||||
val body = response.body()
|
val body = response.body()
|
||||||
if (response.isSuccessful && body != null) {
|
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) }
|
_state.update { it.copy(isLoading = false, error = null) }
|
||||||
onDone(body, existingGuest, phone)
|
onDone(body, null, phone)
|
||||||
} else {
|
} else {
|
||||||
_state.update { it.copy(isLoading = false, error = "Create failed: ${response.code()}") }
|
_state.update { it.copy(isLoading = false, error = "Create failed: ${response.code()}") }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -44,6 +44,7 @@ fun GuestInfoScreen(
|
|||||||
LaunchedEffect(guestId) {
|
LaunchedEffect(guestId) {
|
||||||
viewModel.reset()
|
viewModel.reset()
|
||||||
viewModel.setInitial(initialGuest, initialPhone)
|
viewModel.setInitial(initialGuest, initialPhone)
|
||||||
|
viewModel.loadGuest(propertyId, guestId, initialPhone)
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|||||||
@@ -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) {
|
fun submit(propertyId: String, guestId: String, onDone: () -> Unit) {
|
||||||
if (propertyId.isBlank() || guestId.isBlank()) return
|
if (propertyId.isBlank() || guestId.isBlank()) return
|
||||||
val current = state.value
|
val current = state.value
|
||||||
|
|||||||
Reference in New Issue
Block a user