void flow complete

This commit is contained in:
moon 2026-05-22 00:05:58 +06:30
parent 5c6c8f78c5
commit 767456b29a
6 changed files with 294 additions and 61 deletions

View File

@ -24,6 +24,7 @@ import com.mob.utsmyanmar.ui.sign_on.SignOnResultScreen
import com.mob.utsmyanmar.ui.sign_on.SignOnRoute
import com.mob.utsmyanmar.ui.sending_to_host.SendingToHostRoute
import com.mob.utsmyanmar.ui.transaction_result.TransactionResultRoute
import com.mob.utsmyanmar.ui.sale_void.TranDetailPage
import com.mob.utsmyanmar.ui.sale_void.VoidViewModel
import com.mob.utsmyanmar.ui.sale_void.VoidTraceScreen
import com.mob.utsmyanmar.viewmodel.CardReaderViewModel
@ -88,10 +89,44 @@ fun AppNavGraph(
VoidTraceScreen(
voidViewModel = voidViewModel,
onNavigateTranDetail = { trace ->
navController.navigate(Routes.VoidTranDetail.createRoute(trace)) {
launchSingleTop = true
}
},
onBack = { navController.popBackStack() }
)
}
composable(
route = Routes.VoidTranDetail.route,
arguments = listOf(
navArgument("trace") {
type = NavType.StringType
}
)
) { backStackEntry ->
val voidViewModel: VoidViewModel = hiltViewModel()
val sharedViewModel: SharedViewModel = hiltViewModel(activity)
val trace = backStackEntry.arguments?.getString("trace").orEmpty()
TranDetailPage(
voidViewModel = voidViewModel,
trace = trace,
onBack = { navController.popBackStack() },
onProceedVoid = { payDetail ->
sharedViewModel.transactionsType.value = com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType.VOID
sharedViewModel.payDetail.value = payDetail
navController.navigate(Routes.SendingToHost.route) {
popUpTo(Routes.VoidTranDetail.route) {
inclusive = true
}
launchSingleTop = true
}
}
)
}
composable(Routes.SignOn.route) {
SignOnRoute(
onBack = { navController.popBackStack() },

View File

@ -9,6 +9,9 @@ sealed class Routes(val route: String) {
}
data object SeeMore : Routes("see_more")
data object VoidTrace : Routes("void_trace")
data object VoidTranDetail : Routes("void_tran_detail/{trace}") {
fun createRoute(trace: String): String = "void_tran_detail/${Uri.encode(trace)}"
}
data object SignOn : Routes("sign_on")
data object SignOnResult : Routes("sign_on_result/{isSuccess}/{message}") {
fun createRoute(isSuccess: Boolean, message: String): String {

View File

@ -0,0 +1,168 @@
package com.mob.utsmyanmar.ui.sale_void
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
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
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.mob.utsmyanmar.ui.components.appbar.AppBar
import com.mob.utsmyanmar.ui.theme.Color
import com.utsmyanmar.paylibs.model.PayDetail
import com.utsmyanmar.paylibs.utils.POSUtil
@Composable
fun TranDetailPage(
voidViewModel: VoidViewModel,
trace: String,
onBack: () -> Unit,
onProceedVoid: (PayDetail) -> Unit
) {
val transaction by voidViewModel.searchTransaction(trace).observeAsState()
Scaffold(
containerColor = Color.IvoryBeige,
topBar = {
AppBar(
title = "Transaction Detail",
icon = Icons.AutoMirrored.Filled.ArrowBack,
onIconClick = onBack
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp)
) {
transaction?.let { payDetail ->
TransactionDetailsCard(transaction = payDetail)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Review the transaction before proceeding to void.",
color = Color.Gray,
fontSize = 13.sp
)
Spacer(modifier = Modifier.weight(1f))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Button(
onClick = onBack,
modifier = Modifier
.weight(1f)
.height(56.dp),
shape = RoundedCornerShape(8.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.White,
contentColor = Color.LegacyRed
)
) {
Text("Back")
}
Button(
onClick = { onProceedVoid(payDetail) },
modifier = Modifier
.weight(1f)
.height(56.dp),
shape = RoundedCornerShape(8.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.LegacyRed,
contentColor = Color.White
)
) {
Text("Void")
}
}
} ?: Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "Transaction not found.",
color = Color.Gray,
fontSize = 14.sp
)
}
}
}
}
@Composable
private fun TransactionDetailsCard(transaction: PayDetail) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = Color.White)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text(
text = "Previous Sale",
color = Color.LegacyRed,
fontWeight = FontWeight.Bold,
fontSize = 16.sp
)
DetailRow("Trace", transaction.voucherNo)
DetailRow("Amount", POSUtil.getInstance().formatAmount(transaction.amount))
DetailRow("Card No", transaction.cardNo)
DetailRow("Reference", transaction.referNo)
DetailRow("Approval", transaction.approvalCode.orEmpty())
DetailRow("Date", transaction.tradeDate)
DetailRow("Time", transaction.tradeTime)
}
}
}
@Composable
private fun DetailRow(
label: String,
value: String
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = label,
color = Color.Gray,
fontSize = 13.sp
)
Text(
text = value.ifBlank { "-" },
color = Color.Black,
fontSize = 13.sp,
fontWeight = FontWeight.Medium
)
}
}

View File

@ -41,17 +41,18 @@ import androidx.lifecycle.MutableLiveData
import com.mob.utsmyanmar.ui.components.appbar.AppBar
import com.mob.utsmyanmar.ui.theme.Color
import com.utsmyanmar.paylibs.model.PayDetail
import com.utsmyanmar.paylibs.utils.POSUtil
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
@Composable
fun VoidTraceScreen(
voidViewModel: VoidViewModel,
onNavigateTranDetail: (String) -> Unit,
onBack: () -> Unit = {}
) {
val tag = "VoidTraceScreen"
var traceNumber by remember { mutableStateOf("") }
var searchedTrace by remember { mutableStateOf("") }
var lastNavigatedTrace by remember { mutableStateOf<String?>(null) }
val displayTraceNumber = traceNumber.padStart(6, '0').ifEmpty { "000000" }
val recentTransactions by voidViewModel.getLastThreeTransactions().observeAsState(emptyList())
@ -79,6 +80,19 @@ fun VoidTraceScreen(
Color.Black
}
LaunchedEffect(searchedTrace) {
if (searchedTrace != lastNavigatedTrace) {
lastNavigatedTrace = null
}
}
LaunchedEffect(transaction?.voucherNo) {
val matchedTrace = transaction?.voucherNo ?: return@LaunchedEffect
if (lastNavigatedTrace == matchedTrace) return@LaunchedEffect
lastNavigatedTrace = matchedTrace
onNavigateTranDetail(matchedTrace)
}
LaunchedEffect(searchedTrace, transaction) {
Log.d(
@ -162,13 +176,7 @@ fun VoidTraceScreen(
}
)
Spacer(modifier = Modifier.height(20.dp))
Column(modifier = Modifier.weight(1f)) {
if (transaction != null) {
TransactionDetailsCard(transaction = transaction!!)
}
}
Spacer(modifier = Modifier.weight(1f))
Row(
modifier = Modifier
@ -303,55 +311,3 @@ private fun TraceKeypadButton(
)
}
}
@Composable
private fun TransactionDetailsCard(transaction: PayDetail) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = Color.White)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text(
text = "Previous Sale",
color = Color.LegacyRed,
fontWeight = FontWeight.Bold,
fontSize = 16.sp
)
DetailRow("Trace", transaction.voucherNo)
DetailRow("Amount", POSUtil.getInstance().formatAmount(transaction.amount))
DetailRow("Card No", transaction.cardNo)
DetailRow("Reference", transaction.referNo)
DetailRow("Approval", transaction.approvalCode.orEmpty())
DetailRow("Date", transaction.tradeDate)
DetailRow("Time", transaction.tradeTime)
}
}
}
@Composable
private fun DetailRow(
label: String,
value: String
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = label,
color = Color.Gray,
fontSize = 13.sp
)
Text(
text = value.ifBlank { "-" },
color = Color.Black,
fontSize = 13.sp,
fontWeight = FontWeight.Medium
)
}
}

View File

@ -13,7 +13,7 @@ fun SendingToHostRoute(
onNavigateTransactionResult: () -> Unit
) {
LaunchedEffect(Unit) {
sharedViewModel.saveMockApprovedSaleForVoidTesting()
sharedViewModel.saveMockHostResultForTesting()
delay(MOCK_HOST_DELAY_MS)
onNavigateTransactionResult()
}

View File

@ -443,6 +443,13 @@ class SharedViewModel @Inject constructor(
repository.insertPayDetail(payDetail)
}
fun saveMockHostResultForTesting() {
when (transactionsType.value) {
TransactionsType.VOID -> saveMockApprovedVoidForTesting()
else -> saveMockApprovedSaleForVoidTesting()
}
}
fun saveMockApprovedSaleForVoidTesting() {
val detail = payDetail.value ?: return
@ -482,6 +489,70 @@ class SharedViewModel @Inject constructor(
approvalCode.value = mockApprovalCode
}
fun saveMockApprovedVoidForTesting() {
val originalSale = payDetail.value ?: return
originalSale.isCanceled = true
repository.updatePayDetail(originalSale)
val systemParams = SystemParamsOperation.getInstance()
val mockTraceNo = systemParams.incrementSerialNum
val mockInvoiceNo = systemParams.incrementInvoiceNum
val mockApprovalCode = mockTraceNo.takeLast(6).padStart(6, '0')
val mockReferenceNo = buildString {
append(SystemDateTime.getYYMMDD())
append(SystemDateTime.getHHmmss())
}.takeLast(12)
val voidDetail = PayDetail().apply {
merchantName = originalSale.merchantName
merchantNo = originalSale.merchantNo
terminalNo = originalSale.terminalNo
CardNo = originalSale.CardNo
cardType = originalSale.cardType
EXPDate = originalSale.EXPDate
cardHolderName = originalSale.cardHolderName
processCode = TransactionsType.VOID.processCode
amount = originalSale.amount
transPlatform = originalSale.transPlatform
transactionType = TransactionType.VOID
transType = TransactionsType.VOID.name
currencyCode = originalSale.currencyCode
currency = originalSale.currency
batchNo = originalSale.batchNo
voucherNo = mockTraceNo
invoiceNo = mockInvoiceNo
referNo = mockReferenceNo
approvalCode = mockApprovalCode
authNo = mockApprovalCode
accountType = originalSale.accountType
tradeAnswerCode = "00"
tradeResultDes = "MOCK VOID APPROVED"
TradeDate = SystemDateTime.getMMDD()
TradeTime = SystemDateTime.getHHmmss()
tradeDateAndTime = SystemDateTime.getMMDDhhmmss()
tradeDateTime = SystemDateTime.getYYMMDDhhmmss()
transDate = SystemDateTime.getTodayDateFormat()
transTime = SystemDateTime.getTodayTimeFormat()
originalPOSNum = originalSale.voucherNo
originalReferNo = originalSale.referNo
originalAuthNo = originalSale.approvalCode
originalBathNo = originalSale.batchNo
originalTransDate = originalSale.TradeDate
originalAmount = originalSale.amount
isCanceled = false
isSettle = false
isNeedReversal = false
isReturnGood = false
}
repository.insertPayDetail(voidDetail)
payDetail.value = voidDetail
traceNo.value = mockTraceNo
rrNNo.value = mockReferenceNo
approvalCode.value = mockApprovalCode
}
fun enableCardStatusIcon(
tapCard: Boolean,
tapDevice: Boolean,