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