admins can delete cash payments

This commit is contained in:
androidlover5842
2026-01-30 11:40:49 +05:30
parent c5e0648dd1
commit 4fc080f146
7 changed files with 120 additions and 23 deletions

View File

@@ -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,

View File

@@ -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>
}

View File

@@ -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)

View File

@@ -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"
)
}
}
}
}
}

View File

@@ -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)
}

View File

@@ -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) }

View File

@@ -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)) {