admins can delete cash payments
This commit is contained in:
@@ -91,6 +91,9 @@ class MainActivity : ComponentActivity() {
|
||||
it == "ADMIN" || it == "MANAGER"
|
||||
} == true
|
||||
}
|
||||
val canDeleteCashPayment: (String) -> Boolean = { propertyId ->
|
||||
state.isSuperAdmin || state.propertyRoles[propertyId]?.contains("ADMIN") == true
|
||||
}
|
||||
|
||||
BackHandler(enabled = currentRoute != AppRoute.Home) {
|
||||
when (currentRoute) {
|
||||
@@ -486,6 +489,7 @@ class MainActivity : ComponentActivity() {
|
||||
propertyId = currentRoute.propertyId,
|
||||
bookingId = currentRoute.bookingId,
|
||||
canAddCash = canManagePayuSettings(currentRoute.propertyId),
|
||||
canDeleteCash = canDeleteCashPayment(currentRoute.propertyId),
|
||||
onBack = {
|
||||
route.value = AppRoute.BookingDetailsTabs(
|
||||
currentRoute.propertyId,
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.android.trisolarispms.data.api.model.PaymentCreateRequest
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
@@ -128,4 +129,11 @@ interface BookingApi {
|
||||
@Path("bookingId") bookingId: String,
|
||||
@Body body: PaymentCreateRequest
|
||||
): Response<PaymentDto>
|
||||
|
||||
@DELETE("properties/{propertyId}/bookings/{bookingId}/payments/{paymentId}")
|
||||
suspend fun deletePayment(
|
||||
@Path("propertyId") propertyId: String,
|
||||
@Path("bookingId") bookingId: String,
|
||||
@Path("paymentId") paymentId: String
|
||||
): Response<Unit>
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.android.trisolarispms.ui.payment
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -12,6 +13,7 @@ import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
@@ -31,6 +33,7 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -45,6 +48,7 @@ fun BookingPaymentsScreen(
|
||||
propertyId: String,
|
||||
bookingId: String,
|
||||
canAddCash: Boolean,
|
||||
canDeleteCash: Boolean,
|
||||
onBack: () -> Unit,
|
||||
viewModel: BookingPaymentsViewModel = viewModel()
|
||||
) {
|
||||
@@ -87,6 +91,7 @@ fun BookingPaymentsScreen(
|
||||
onClick = {
|
||||
val amount = amountInput.value.toLongOrNull() ?: 0L
|
||||
viewModel.addCashPayment(propertyId, bookingId, amount)
|
||||
amountInput.value = ""
|
||||
},
|
||||
enabled = !state.isLoading,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
@@ -108,10 +113,18 @@ fun BookingPaymentsScreen(
|
||||
}
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
) {
|
||||
items(state.payments) { payment ->
|
||||
PaymentCard(payment = payment)
|
||||
PaymentCard(
|
||||
payment = payment,
|
||||
canDeleteCash = canDeleteCash,
|
||||
onDelete = { paymentId ->
|
||||
viewModel.deleteCashPayment(propertyId, bookingId, paymentId)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +132,11 @@ fun BookingPaymentsScreen(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PaymentCard(payment: PaymentDto) {
|
||||
private fun PaymentCard(
|
||||
payment: PaymentDto,
|
||||
canDeleteCash: Boolean,
|
||||
onDelete: (String) -> Unit
|
||||
) {
|
||||
val date = payment.receivedAt?.let {
|
||||
runCatching { OffsetDateTime.parse(it) }.getOrNull()
|
||||
}
|
||||
@@ -136,19 +153,52 @@ private fun PaymentCard(payment: PaymentDto) {
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
val amountText = buildString {
|
||||
append(payment.amount?.toString() ?: "-")
|
||||
payment.currency?.let { append(" $it") }
|
||||
}
|
||||
Text(text = amountText, style = MaterialTheme.typography.titleMedium)
|
||||
if (canDeleteCash && isCash && !payment.id.isNullOrBlank()) {
|
||||
IconButton(onClick = { onDelete(payment.id) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = "Delete cash payment"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val methodColor = if (isCash) {
|
||||
MaterialTheme.colorScheme.tertiary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.secondary
|
||||
}
|
||||
val upiColor = MaterialTheme.colorScheme.primary
|
||||
val payerColor = MaterialTheme.colorScheme.secondary
|
||||
payment.method?.let {
|
||||
Text(text = "Method: $it", style = MaterialTheme.typography.bodySmall)
|
||||
Text(
|
||||
text = "Method: $it",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = methodColor
|
||||
)
|
||||
}
|
||||
payment.payerVpa?.takeIf { it.isNotBlank() }?.let {
|
||||
Text(text = "UPI: $it", style = MaterialTheme.typography.bodySmall)
|
||||
Text(
|
||||
text = "UPI: $it",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = upiColor
|
||||
)
|
||||
}
|
||||
payment.payerName?.takeIf { it.isNotBlank() }?.let {
|
||||
Text(text = "Payer: $it", style = MaterialTheme.typography.bodySmall)
|
||||
Text(
|
||||
text = "Payer: $it",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = payerColor
|
||||
)
|
||||
}
|
||||
dateText?.let {
|
||||
Text(text = "Received: $it", style = MaterialTheme.typography.bodySmall)
|
||||
|
||||
@@ -87,4 +87,41 @@ class BookingPaymentsViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteCashPayment(propertyId: String, bookingId: String, paymentId: String) {
|
||||
viewModelScope.launch {
|
||||
_state.update { it.copy(isLoading = true, error = null) }
|
||||
try {
|
||||
val api = ApiClient.create()
|
||||
val response = api.deletePayment(
|
||||
propertyId = propertyId,
|
||||
bookingId = bookingId,
|
||||
paymentId = paymentId
|
||||
)
|
||||
if (response.isSuccessful) {
|
||||
_state.update { current ->
|
||||
current.copy(
|
||||
isLoading = false,
|
||||
payments = current.payments.filterNot { it.id == paymentId },
|
||||
error = null
|
||||
)
|
||||
}
|
||||
} else {
|
||||
_state.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
error = "Delete failed: ${response.code()}"
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
error = e.localizedMessage ?: "Delete failed"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
@@ -59,6 +60,10 @@ fun PayuQrScreen(
|
||||
val clipboard = LocalClipboardManager.current
|
||||
val context = LocalContext.current
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
onDispose { viewModel.reset() }
|
||||
}
|
||||
|
||||
LaunchedEffect(pendingAmount) {
|
||||
viewModel.setInitialAmount(pendingAmount)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ class PayuQrViewModel : ViewModel() {
|
||||
)
|
||||
val state: StateFlow<PayuQrState> = _state
|
||||
|
||||
fun reset() {
|
||||
_state.value = PayuQrState(deviceInfo = buildDeviceInfo())
|
||||
}
|
||||
|
||||
fun onAmountChange(value: String) {
|
||||
val digits = value.filter { it.isDigit() }
|
||||
_state.update { it.copy(amountInput = digits, error = null) }
|
||||
|
||||
@@ -126,9 +126,7 @@ fun ActiveRoomStaysScreen(
|
||||
items(state.checkedInBookings) { booking ->
|
||||
CheckedInBookingCard(
|
||||
booking = booking,
|
||||
onClick = { selectedBooking.value = booking },
|
||||
onLongClick = { onOpenBookingDetails(booking) }
|
||||
)
|
||||
onClick = { onOpenBookingDetails(booking) })
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
@@ -152,12 +150,6 @@ fun ActiveRoomStaysScreen(
|
||||
) {
|
||||
Text("Manage room stay")
|
||||
}
|
||||
TextButton(onClick = { selectedBooking.value = null }) {
|
||||
Text("Balance")
|
||||
}
|
||||
TextButton(onClick = { selectedBooking.value = null }) {
|
||||
Text("Add photos")
|
||||
}
|
||||
TextButton(onClick = { selectedBooking.value = null }) {
|
||||
Text("Checkout")
|
||||
}
|
||||
@@ -173,14 +165,11 @@ fun ActiveRoomStaysScreen(
|
||||
@Composable
|
||||
private fun CheckedInBookingCard(
|
||||
booking: BookingListItem,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit
|
||||
) {
|
||||
onClick: () -> Unit) {
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
|
||||
modifier = Modifier.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
|
||||
Reference in New Issue
Block a user