diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt index 50382ca..2a56317 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/dashboard/DashboardScreen.kt @@ -107,6 +107,7 @@ fun DashboardScreen2( onNavigateSettlement: () -> Unit = {}, onNavigateVersion: () -> Unit = {}, onNavigateFunctions: () -> Unit = {}, + onNavigateHistory: () -> Unit = {}, onNavigateAction: (String) -> Unit = {}, onNavigateNotifications: () -> Unit = {}, dashboardUiState: DashboardUiState = DashboardUiState(), @@ -455,6 +456,7 @@ fun DashboardScreen2( onNavigateAmount = onNavigateAmount, onNavigateSignOn = onNavigateSignOn, onNavigateSettlement = onNavigateSettlement, + onNavigateHistory = onNavigateHistory, onNavigateAction = onNavigateAction ), modifier = Modifier @@ -675,6 +677,7 @@ private fun buildMenuItems( onNavigateAmount: (String) -> Unit, onNavigateSignOn: () -> Unit, onNavigateSettlement: () -> Unit, + onNavigateHistory: () -> Unit, onNavigateAction: (String) -> Unit ): List = listOf( DashboardMenuItem( @@ -705,7 +708,7 @@ private fun buildMenuItems( modifier = Modifier.size(32.dp), tint = Color.LegacyRed ) - }) { }, + }) { onNavigateHistory() }, DashboardMenuItem( "Sign On", { diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/BatchTotalScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/BatchTotalScreen.kt new file mode 100644 index 0000000..e22d62a --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/BatchTotalScreen.kt @@ -0,0 +1,87 @@ +package com.mob.utsmyanmar.ui.history_functions + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +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.unit.dp +import com.mob.utsmyanmar.ui.components.appbar.AppBar +import com.mob.utsmyanmar.ui.theme.Color +import com.utsmyanmar.paylibs.utils.POSUtil +import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation + +@Composable +fun BatchTotalScreen( + viewModel: HistoryViewModel, + onBack: () -> Unit, +) { + val batchNo = SystemParamsOperation.getInstance().currentBatchNum + val records by viewModel.getReviewBatchTransaction(batchNo).observeAsState(emptyList()) + val totalAmount = records.sumOf { detailReportSignedAmount(it) } + + Scaffold( + containerColor = Color.IvoryBeige, + topBar = { + AppBar( + title = "Batch Total", + icon = Icons.AutoMirrored.Filled.ArrowBack, + onIconClick = onBack + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(16.dp) + ) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = Color.White) + ) { + Column(modifier = Modifier.padding(16.dp)) { + SummaryLine("Batch No", batchNo) + SummaryLine("Record Count", records.size.toString()) + SummaryLine("Total Amount", POSUtil.getInstance().formatAmount(totalAmount)) + } + } + + Spacer(modifier = Modifier.height(12.dp)) + + if (records.isEmpty()) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text("No batch records found.", color = Color.Gray) + } + } else { + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(records, key = { "${it.voucherNo}_${it.PID}" }) { record -> + HistoryTransactionCard(record, onClick = null) + } + } + } + } + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/DetailReportScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/DetailReportScreen.kt new file mode 100644 index 0000000..75513e4 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/DetailReportScreen.kt @@ -0,0 +1,162 @@ +package com.mob.utsmyanmar.ui.history_functions + +import android.widget.Toast +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +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.CircularProgressIndicator +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.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.mob.utsmyanmar.ui.components.appbar.AppBar +import com.mob.utsmyanmar.ui.theme.Color +import com.utsmyanmar.paylibs.print.PrintHelper +import com.utsmyanmar.paylibs.print.printx.PrintXReceipt +import com.utsmyanmar.paylibs.print.printx.PrintXStatus +import com.utsmyanmar.paylibs.utils.POSUtil +import com.utsmyanmar.paylibs.utils.enums.HostType +import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType +import com.utsmyanmar.paylibs.utils.params.Params + +@Composable +fun DetailReportScreen( + viewModel: HistoryViewModel, + hostType: HostType, + onBack: () -> Unit, +) { + val context = LocalContext.current + val allTransactions by viewModel.getTransactionHistory().observeAsState(emptyList()) + var isPrinting by remember { mutableStateOf(false) } + + val records = remember(allTransactions, hostType) { + filterDetailReportTransactions(allTransactions, hostType) + } + val totalAmount = remember(records) { + records.sumOf(::detailReportSignedAmount) + } + + fun printDetailReport() { + if (records.isEmpty()) { + Toast.makeText(context, "No transactions to print.", Toast.LENGTH_SHORT).show() + return + } + if (PrintHelper.getInstance().paperRollStatus() != 1) { + Toast.makeText(context, "Paper roll not ready.", Toast.LENGTH_SHORT).show() + return + } + + val tradeData = Params.newTrade(false) + val payDetail = tradeData.payDetail + payDetail.transType = TransactionsType.DETAIL_REPORT.name + payDetail.transactionType = TransactionsType.DETAIL_REPORT.value + + isPrinting = true + PrintXReceipt.getInstance().printDetailReport( + payDetail, + ArrayList(records), + hostType, + object : PrintXStatus { + override fun onSuccess() { + isPrinting = false + } + + override fun onFailure() { + isPrinting = false + Toast.makeText(context, "Print failed.", Toast.LENGTH_SHORT).show() + } + } + ) + } + + Scaffold( + containerColor = Color.IvoryBeige, + topBar = { + AppBar( + title = "Print Detail Report", + icon = Icons.AutoMirrored.Filled.ArrowBack, + onIconClick = onBack + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(16.dp) + ) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = Color.White) + ) { + Column(modifier = Modifier.padding(16.dp)) { + SummaryLine("Host", hostTypeLabel(hostType)) + SummaryLine("Card Type", hostTypeLabel(hostType)) + SummaryLine("Transaction Count", records.size.toString()) + SummaryLine("Total Amount", POSUtil.getInstance().formatAmount(totalAmount)) + } + } + + Spacer(modifier = Modifier.height(12.dp)) + + if (records.isEmpty()) { + Box( + modifier = Modifier.weight(1f), + contentAlignment = Alignment.Center + ) { + Text("No transactions available.", color = Color.Gray) + } + } else { + LazyColumn( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(records, key = { "${it.voucherNo}_${it.PID}" }) { record -> + HistoryTransactionCard(record, onClick = null) + } + } + } + + Spacer(modifier = Modifier.height(12.dp)) + + Button( + onClick = ::printDetailReport, + enabled = records.isNotEmpty() && !isPrinting, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.LegacyRed, + contentColor = Color.White + ) + ) { + if (isPrinting) { + CircularProgressIndicator(color = Color.White) + } else { + Text("Print") + } + } + } + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryComponents.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryComponents.kt new file mode 100644 index 0000000..7a5160a --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryComponents.kt @@ -0,0 +1,191 @@ +package com.mob.utsmyanmar.ui.history_functions + +import androidx.compose.foundation.clickable +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.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.rounded.Backspace +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.theme.Color +import com.utsmyanmar.paylibs.model.PayDetail +import com.utsmyanmar.paylibs.utils.POSUtil + +@Composable +internal fun HistoryTransactionCard( + payDetail: PayDetail, + onClick: (() -> Unit)?, +) { + Card( + modifier = Modifier + .fillMaxWidth() + .then( + if (onClick != null) { + Modifier.clickable(onClick = onClick) + } else { + Modifier + } + ), + colors = CardDefaults.cardColors(containerColor = Color.White) + ) { + Column(modifier = Modifier.padding(16.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = payDetail.transType.ifBlank { "UNKNOWN" }, + color = Color.LegacyRed, + fontWeight = FontWeight.Bold, + fontSize = 14.sp + ) + Text( + text = POSUtil.getInstance().formatAmount(payDetail.amount), + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp + ) + } + Spacer(modifier = Modifier.height(4.dp)) + Text("Trace ${payDetail.voucherNo.ifBlank { "-" }}", fontSize = 12.sp) + Text("RRN ${payDetail.referNo.ifBlank { "-" }}", fontSize = 12.sp, color = Color.Gray) + Text( + "${payDetail.transDate} ${payDetail.transTime}", + fontSize = 12.sp, + color = Color.Gray + ) + } + } +} + +@Composable +internal fun HistoryTransactionDetailCard(payDetail: PayDetail) { + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = Color.White) + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = payDetail.transType.ifBlank { "Transaction" }, + color = Color.LegacyRed, + fontWeight = FontWeight.Bold, + fontSize = 16.sp + ) + SummaryLine("Trace", payDetail.voucherNo) + SummaryLine("Amount", POSUtil.getInstance().formatAmount(payDetail.amount)) + SummaryLine("Card No", payDetail.cardNo) + SummaryLine("Reference", payDetail.referNo) + SummaryLine("Approval", payDetail.approvalCode.orEmpty()) + SummaryLine("Date", payDetail.tradeDate) + SummaryLine("Time", payDetail.tradeTime) + SummaryLine("Status", payDetail.tradeAnswerCode.orEmpty()) + } + } +} + +@Composable +internal fun SummaryLine(label: String, value: String) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(label, color = Color.Gray, fontSize = 13.sp) + Text( + value.ifBlank { "-" }, + fontSize = 13.sp, + fontWeight = FontWeight.Medium + ) + } +} + +@Composable +internal fun SummaryPickerRow( + label: String, + value: String, + onClick: () -> Unit, +) { + Card( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick), + colors = CardDefaults.cardColors(containerColor = Color.White) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text(label, fontWeight = FontWeight.Medium) + Text(value, color = Color.Gray) + } + } +} + +@Composable +internal fun HistoryNumericKeypad( + onDigit: (String) -> Unit, + onBackspace: () -> Unit, + onClear: () -> Unit, +) { + val rows = listOf( + listOf("1", "2", "3"), + listOf("4", "5", "6"), + listOf("7", "8", "9"), + listOf("C", "0", "⌫") + ) + + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + rows.forEach { row -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + row.forEach { key -> + Button( + onClick = { + when (key) { + "C" -> onClear() + "⌫" -> onBackspace() + else -> onDigit(key) + } + }, + modifier = Modifier + .weight(1f) + .height(56.dp), + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = Color.Black + ) + ) { + if (key == "⌫") { + Icon(Icons.Rounded.Backspace, contentDescription = "Backspace") + } else { + Text(key, fontSize = 20.sp, fontWeight = FontWeight.Bold) + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryFilters.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryFilters.kt new file mode 100644 index 0000000..2d7e9ca --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryFilters.kt @@ -0,0 +1,141 @@ +package com.mob.utsmyanmar.ui.history_functions + +import com.utsmyanmar.paylibs.model.PayDetail +import com.utsmyanmar.paylibs.utils.POSUtil +import com.utsmyanmar.paylibs.utils.enums.HostType +import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +enum class HistoryHostPurpose { + REPRINT_ANY, + LAST_SETTLEMENT, + DETAIL_REPORT, + SUMMARY_REPORT, +} + +internal fun prepareReprintPayDetail(payDetail: PayDetail): PayDetail { + val suffix = "(REPRINT)" + payDetail.transType = if (payDetail.transType.length >= 20) { + "${payDetail.transType}\n$suffix" + } else { + payDetail.transType + suffix + } + return payDetail +} + +internal fun isValidForReprint(payDetail: PayDetail): Boolean { + if (payDetail.transactionType == TransactionsType.SETTLEMENT.value) return false + if (payDetail.transactionType == TransactionsType.WAVEPAY.value && payDetail.qrTransStatus != 1) { + return false + } + if (payDetail.transactionType == TransactionsType.WAVEPAY_REFUND.value && payDetail.qrTransStatus != 1) { + return false + } + return true +} + +internal fun filterReprintAnyTransactions( + transactions: List, + hostType: HostType +): List { + val yesterday = POSUtil.getInstance().yesterdayDate + val dateFormat = SimpleDateFormat("dd/MM/yy", Locale.getDefault()) + + return transactions.filter { pay -> + val transDate = parseTransDate(pay.transDate, dateFormat) ?: return@filter false + if (yesterday.compareTo(transDate) > 0) return@filter false + if (pay.transactionType == TransactionsType.SETTLEMENT.value) return@filter false + if (pay.transactionType == TransactionsType.REVERSAL.value) return@filter false + + when (hostType) { + HostType.MPU -> + pay.accountType == "MPU" && + pay.transactionType != TransactionsType.WAVEPAY.value && + pay.transactionType != TransactionsType.WAVEPAY_REFUND.value + + HostType.QR -> + (pay.transactionType == TransactionsType.WAVEPAY.value || + pay.transactionType == TransactionsType.WAVEPAY_REFUND.value) && + pay.qrTransStatus == 1 + + HostType.VISA_MASTER -> + pay.accountType != "MPU" && + pay.transactionType != TransactionsType.WAVEPAY.value && + pay.transactionType != TransactionsType.WAVEPAY_REFUND.value + } + } +} + +internal fun filterDetailReportTransactions( + transactions: List, + hostType: HostType +): List { + val yesterday = POSUtil.getInstance().yesterdayDate + + return transactions.filter { pay -> + if (POSUtil.getInstance().getDateByString(pay.transDate).before(yesterday)) return@filter false + if (pay.transactionType == TransactionsType.REVERSAL.value) return@filter false + if (pay.transactionType == TransactionsType.SETTLEMENT.value) return@filter false + + when (hostType) { + HostType.MPU -> + pay.accountType == "MPU" && + pay.transactionType != TransactionsType.WAVEPAY_REFUND.value && + pay.transactionType != TransactionsType.WAVEPAY.value + + HostType.QR -> + (pay.transactionType == TransactionsType.WAVEPAY.value || + pay.transactionType == TransactionsType.WAVEPAY_REFUND.value) && + pay.qrTransStatus == 1 + + HostType.VISA_MASTER -> + pay.accountType != "MPU" && + pay.transactionType != TransactionsType.WAVEPAY_REFUND.value && + pay.transactionType != TransactionsType.WAVEPAY.value + } + } +} + +internal fun detailReportSignedAmount(payDetail: PayDetail): Long { + val isNegative = payDetail.transactionType == TransactionsType.VOID.value || + payDetail.transactionType == TransactionsType.REFUND.value || + payDetail.transactionType == TransactionsType.WAVEPAY_REFUND.value + return if (isNegative) -payDetail.amount else payDetail.amount +} + +internal fun hostTypeLabel(hostType: HostType): String = when (hostType) { + HostType.QR -> "QR PAY" + HostType.MPU -> "MPU" + HostType.VISA_MASTER -> "VISA/MASTER" +} + +internal fun hostTypeAccountLabel(hostType: HostType): String = when (hostType) { + HostType.QR -> "WALLET" + HostType.MPU -> "MPU" + HostType.VISA_MASTER -> "Visa/Master" +} + +internal fun searchReprintAnyTransactions( + transactions: List, + query: String +): List { + if (query.isBlank()) return transactions + return when (query.length) { + 6 -> transactions.filter { it.voucherNo == query } + 12 -> transactions.filter { it.referNo == query } + else -> transactions.filter { + it.voucherNo.contains(query) || it.referNo.contains(query) + } + } +} + +private fun parseTransDate(value: String, formatter: SimpleDateFormat): Date? { + return try { + formatter.parse(value) + } catch (_: ParseException) { + null + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryMenuScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryMenuScreen.kt new file mode 100644 index 0000000..85785b3 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryMenuScreen.kt @@ -0,0 +1,78 @@ +package com.mob.utsmyanmar.ui.history_functions + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.ChevronRight +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.mob.utsmyanmar.ui.components.appbar.AppBar +import com.mob.utsmyanmar.ui.functions.FunctionButton +import com.mob.utsmyanmar.ui.theme.Color + +@Composable +fun HistoryMenuScreen( + onBack: () -> Unit, + onReprintLastTransaction: () -> Unit, + onReprintAnyTransaction: () -> Unit, + onReprintLastSettlement: () -> Unit, + onPrintDetailReport: () -> Unit, + onPrintSummaryReport: () -> Unit, + onReview: () -> Unit, + onBatchTotal: () -> Unit, +) { + Scaffold( + containerColor = Color.IvoryBeige, + topBar = { + AppBar( + title = "Select Function", + icon = Icons.AutoMirrored.Filled.ArrowBack, + onIconClick = onBack + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(16.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + HistoryMenuButton("Reprint Last Transaction", onReprintLastTransaction) + HistoryMenuButton("Reprint Any Transaction", onReprintAnyTransaction) + HistoryMenuButton("Reprint Last Settlement Report", onReprintLastSettlement) + HistoryMenuButton("Print Detail Report", onPrintDetailReport) + HistoryMenuButton("Print Summary Report", onPrintSummaryReport) + HistoryMenuButton("Review", onReview) + HistoryMenuButton("Batch Total", onBatchTotal) + } + } +} + +@Composable +private fun HistoryMenuButton( + title: String, + onClick: () -> Unit, +) { + FunctionButton( + onClick = onClick, + title = title, + subTitle = "", + trailingIcon = { + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = null, + tint = Color.LegacyRed + ) + } + ) +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryPrintUtils.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryPrintUtils.kt new file mode 100644 index 0000000..71b513e --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryPrintUtils.kt @@ -0,0 +1,34 @@ +package com.mob.utsmyanmar.ui.history_functions + +import android.content.Context +import android.widget.Toast +import com.utsmyanmar.paylibs.model.PayDetail +import com.utsmyanmar.paylibs.print.PrintHelper +import com.utsmyanmar.paylibs.print.printx.PrintXReceipt +import com.utsmyanmar.paylibs.print.printx.PrintXStatus + +internal fun printSettlementReport( + context: Context, + payDetail: PayDetail, + onSuccess: () -> Unit, + onFailure: () -> Unit, +) { + if (PrintHelper.getInstance().paperRollStatus() != 1) { + Toast.makeText(context, "Paper roll not ready.", Toast.LENGTH_SHORT).show() + onFailure() + return + } + + PrintXReceipt.getInstance().printSmileSettlementReport( + payDetail, + object : PrintXStatus { + override fun onSuccess() { + onSuccess() + } + + override fun onFailure() { + onFailure() + } + } + ) +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryRoute.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryRoute.kt new file mode 100644 index 0000000..a6ae7a9 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryRoute.kt @@ -0,0 +1,180 @@ +package com.mob.utsmyanmar.ui.history_functions + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.hilt.navigation.compose.hiltViewModel +import com.mob.utsmyanmar.viewmodel.SharedViewModel +import com.utsmyanmar.paylibs.model.PayDetail +import com.utsmyanmar.paylibs.utils.PrintStatus +import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation +import com.utsmyanmar.paylibs.utils.enums.HostType +import com.utsmyanmar.paylibs.utils.enums.TransMenu +import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType + +private sealed interface HistoryDestination { + data object Menu : HistoryDestination + data class SelectHost(val purpose: HistoryHostPurpose) : HistoryDestination + data class ReprintAny(val hostType: HostType) : HistoryDestination + data class DetailReport(val hostType: HostType) : HistoryDestination + data class SummaryReport(val hostType: HostType) : HistoryDestination + data object ReviewTrace : HistoryDestination + data class ReviewDetail(val trace: String) : HistoryDestination + data object BatchTotal : HistoryDestination + data object ReprintSettlement : HistoryDestination + data object NotFound : HistoryDestination +} + +@Composable +fun HistoryRoute( + sharedViewModel: SharedViewModel, + onBack: () -> Unit, + onNavigatePrintReceipt: () -> Unit, + viewModel: HistoryViewModel = hiltViewModel(), +) { + var destination by remember { mutableStateOf(HistoryDestination.Menu) } + var hostPurpose by remember { mutableStateOf(HistoryHostPurpose.REPRINT_ANY) } + + val voucherNo = SystemParamsOperation.getInstance().lastSuccessTrnx + val lastTransactions by viewModel.getLastTransaction(voucherNo).observeAsState(emptyList()) + + fun navigateToHostSelection(purpose: HistoryHostPurpose) { + hostPurpose = purpose + destination = HistoryDestination.SelectHost(purpose) + } + + fun handleHostSelected(hostType: HostType) { + sharedViewModel.hostType.value = hostType + destination = when (hostPurpose) { + HistoryHostPurpose.REPRINT_ANY -> + HistoryDestination.ReprintAny(hostType) + + HistoryHostPurpose.LAST_SETTLEMENT -> { + if (hostType == HostType.MPU) { + HistoryDestination.ReprintSettlement + } else { + HistoryDestination.NotFound + } + } + + HistoryHostPurpose.DETAIL_REPORT -> + HistoryDestination.DetailReport(hostType) + + HistoryHostPurpose.SUMMARY_REPORT -> + HistoryDestination.SummaryReport(hostType) + } + } + + fun reprintTransaction(payDetail: PayDetail) { + sharedViewModel.setTransMenu(TransMenu.REPRINT) + sharedViewModel.isReprint.value = true + sharedViewModel.payDetail.value = payDetail + sharedViewModel.setPrintStatus(PrintStatus.FIRST_PRINT) + onNavigatePrintReceipt() + } + + fun reprintLastTransaction() { + sharedViewModel.setTransMenu(TransMenu.REPRINT) + if (lastTransactions.isEmpty()) { + destination = HistoryDestination.NotFound + return + } + + val payDetail = lastTransactions.last() + if (!isValidForReprint(payDetail)) { + destination = HistoryDestination.NotFound + return + } + + reprintTransaction(prepareReprintPayDetail(payDetail)) + } + + when (val current = destination) { + HistoryDestination.Menu -> HistoryMenuScreen( + onBack = onBack, + onReprintLastTransaction = ::reprintLastTransaction, + onReprintAnyTransaction = { + sharedViewModel.setTransMenu(TransMenu.TRANSACTIONS) + sharedViewModel.transactionsType.value = null + navigateToHostSelection(HistoryHostPurpose.REPRINT_ANY) + }, + onReprintLastSettlement = { + sharedViewModel.setTransMenu(TransMenu.LAST_SETTLEMENT) + navigateToHostSelection(HistoryHostPurpose.LAST_SETTLEMENT) + }, + onPrintDetailReport = { + sharedViewModel.setTransMenu(TransMenu.DETAIL_REPORT) + navigateToHostSelection(HistoryHostPurpose.DETAIL_REPORT) + }, + onPrintSummaryReport = { + sharedViewModel.setTransMenu(TransMenu.SUMMARY_REPORT) + navigateToHostSelection(HistoryHostPurpose.SUMMARY_REPORT) + }, + onReview = { + sharedViewModel.setTransMenu(TransMenu.REVIEW) + sharedViewModel.transactionsType.value = TransactionsType.REVIEW_TRANS + destination = HistoryDestination.ReviewTrace + }, + onBatchTotal = { + sharedViewModel.setTransMenu(TransMenu.REVIEW_BATCH) + destination = HistoryDestination.BatchTotal + } + ) + + is HistoryDestination.SelectHost -> SelectHostScreen( + onBack = { destination = HistoryDestination.Menu }, + onSelectHost = ::handleHostSelected + ) + + is HistoryDestination.ReprintAny -> ReprintAnyTransactionScreen( + viewModel = viewModel, + hostType = current.hostType, + onBack = { destination = HistoryDestination.Menu }, + onReprintTransaction = ::reprintTransaction + ) + + is HistoryDestination.DetailReport -> DetailReportScreen( + viewModel = viewModel, + hostType = current.hostType, + onBack = { destination = HistoryDestination.Menu } + ) + + is HistoryDestination.SummaryReport -> SummaryReportScreen( + viewModel = viewModel, + hostType = current.hostType, + onBack = { destination = HistoryDestination.Menu } + ) + + HistoryDestination.ReviewTrace -> ReviewTraceScreen( + viewModel = viewModel, + onBack = { destination = HistoryDestination.Menu }, + onNavigateReviewDetail = { trace -> + destination = HistoryDestination.ReviewDetail(trace) + } + ) + + is HistoryDestination.ReviewDetail -> ReviewDetailScreen( + viewModel = viewModel, + trace = current.trace, + onBack = { destination = HistoryDestination.ReviewTrace } + ) + + HistoryDestination.BatchTotal -> BatchTotalScreen( + viewModel = viewModel, + onBack = { destination = HistoryDestination.Menu } + ) + + HistoryDestination.ReprintSettlement -> ReprintSettlementScreen( + viewModel = viewModel, + onBack = { destination = HistoryDestination.Menu }, + onDone = onBack + ) + + HistoryDestination.NotFound -> TransactionNotFoundScreen( + onBack = { destination = HistoryDestination.Menu } + ) + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistorySummaryUtils.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistorySummaryUtils.kt new file mode 100644 index 0000000..a0a4c4a --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistorySummaryUtils.kt @@ -0,0 +1,124 @@ +package com.mob.utsmyanmar.ui.history_functions + +import com.utsmyanmar.paylibs.model.PayDetail +import com.utsmyanmar.paylibs.model.SettleData +import com.utsmyanmar.paylibs.utils.POSUtil +import com.utsmyanmar.paylibs.utils.enums.HostType +import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +internal fun formatTime(hour: String, minute: String): String { + if (hour.isBlank() || minute.isBlank()) return "Select" + return "$hour:$minute" +} + +internal class SummaryCounter { + var count = 0 + var amount = 0L + + fun inc(value: Long) { + count++ + amount += value + } +} + +internal fun buildSummarySettleData( + transactions: List, + hostType: HostType, + start: Date, + end: Date, +): SettleData { + val dateFormat = SimpleDateFormat("dd/MM/yy", Locale.getDefault()) + val dateTimeFormat = SimpleDateFormat("dd/MM/yy HH:mm", Locale.getDefault()) + val yesterday = POSUtil.getInstance().yesterdayDate + + val wave = SummaryCounter() + val waveRefund = SummaryCounter() + val voidTran = SummaryCounter() + val sale = SummaryCounter() + val ca = SummaryCounter() + val preAuth = SummaryCounter() + val preAuthVoid = SummaryCounter() + val preAuthComp = SummaryCounter() + val preAuthCompVoid = SummaryCounter() + val refund = SummaryCounter() + + for (pay in transactions) { + val transDate = try { + dateFormat.parse(pay.transDate) + } catch (_: ParseException) { + continue + } ?: continue + + if (yesterday.after(transDate)) continue + + val currentDate = try { + dateTimeFormat.parse("${pay.transDate} ${pay.transTime}") + } catch (_: ParseException) { + continue + } ?: continue + + if (currentDate.before(start) || currentDate.after(end)) continue + + when (hostType) { + HostType.QR -> when (pay.transactionType) { + TransactionsType.WAVEPAY.value -> + if (pay.qrTransStatus == 1) wave.inc(pay.amount) + + TransactionsType.WAVEPAY_REFUND.value -> + if (pay.qrTransStatus == 1) waveRefund.inc(pay.amount) + } + + HostType.MPU -> if (pay.accountType == "MPU") { + accumulateCardSummary( + pay, sale, voidTran, ca, preAuth, preAuthVoid, preAuthComp, preAuthCompVoid, refund + ) + } + + HostType.VISA_MASTER -> if (pay.accountType != "MPU") { + accumulateCardSummary( + pay, sale, voidTran, ca, preAuth, preAuthVoid, preAuthComp, preAuthCompVoid, refund + ) + } + } + } + + return SettleData( + sale.count, sale.amount, + wave.count, wave.amount, + voidTran.count, voidTran.amount, + ca.count, ca.amount, + preAuth.count, preAuth.amount, + preAuthVoid.count, preAuthVoid.amount, + preAuthComp.count, preAuthComp.amount, + preAuthCompVoid.count, preAuthCompVoid.amount, + refund.count, refund.amount, + waveRefund.count, waveRefund.amount + ) +} + +private fun accumulateCardSummary( + pay: PayDetail, + sale: SummaryCounter, + voidTran: SummaryCounter, + ca: SummaryCounter, + preAuth: SummaryCounter, + preAuthVoid: SummaryCounter, + preAuthComp: SummaryCounter, + preAuthCompVoid: SummaryCounter, + refund: SummaryCounter, +) { + when (pay.transactionType) { + TransactionsType.VOID.value -> voidTran.inc(pay.amount) + TransactionsType.SALE.value -> sale.inc(pay.amount) + TransactionsType.CASH_OUT.value -> ca.inc(pay.amount) + TransactionsType.PRE_AUTH_SALE.value -> preAuth.inc(pay.amount) + TransactionsType.PRE_AUTH_VOID.value -> preAuthVoid.inc(pay.amount) + TransactionsType.PRE_AUTH_COMPLETE.value -> preAuthComp.inc(pay.amount) + TransactionsType.PRE_AUTH_COMPLETE_VOID.value -> preAuthCompVoid.inc(pay.amount) + TransactionsType.REFUND.value -> refund.inc(pay.amount) + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryViewModel.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryViewModel.kt new file mode 100644 index 0000000..95be02f --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/HistoryViewModel.kt @@ -0,0 +1,32 @@ +package com.mob.utsmyanmar.ui.history_functions + +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import com.utsmyanmar.baselib.repo.Repository +import com.utsmyanmar.paylibs.model.PayDetail +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class HistoryViewModel @Inject constructor( + private val repository: Repository +) : ViewModel() { + + fun getTransactionHistory(): LiveData> = + repository.getTransactionHistory() + + fun getLastTransaction(voucherNo: String): LiveData> = + repository.getLastTransaction(voucherNo) + + fun getLastSettlement(voucherNo: String): LiveData> = + repository.getLastSettlement(voucherNo) + + fun getReviewBatchTransaction(batchNo: String): LiveData> = + repository.getReviewBatchTransaction(batchNo) + + fun getSmileSummaryTrans(batchNo: String): LiveData> = + repository.getSmileSummaryTrans(batchNo) + + fun searchTransaction(voucherNo: String): LiveData = + repository.searchTransaction(voucherNo) +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/ReprintAnyTransactionScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/ReprintAnyTransactionScreen.kt new file mode 100644 index 0000000..3002a9b --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/ReprintAnyTransactionScreen.kt @@ -0,0 +1,94 @@ +package com.mob.utsmyanmar.ui.history_functions + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.OutlinedTextField +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.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +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.enums.HostType + +@Composable +fun ReprintAnyTransactionScreen( + viewModel: HistoryViewModel, + hostType: HostType, + onBack: () -> Unit, + onReprintTransaction: (PayDetail) -> Unit, +) { + val allTransactions by viewModel.getTransactionHistory().observeAsState(emptyList()) + var searchQuery by remember { mutableStateOf("") } + + val filtered = remember(allTransactions, hostType, searchQuery) { + val base = filterReprintAnyTransactions(allTransactions, hostType) + searchReprintAnyTransactions(base, searchQuery.trim()) + } + + Scaffold( + containerColor = Color.IvoryBeige, + topBar = { + AppBar( + title = "Transactions", + icon = Icons.AutoMirrored.Filled.ArrowBack, + onIconClick = onBack + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(16.dp) + ) { + OutlinedTextField( + value = searchQuery, + onValueChange = { searchQuery = it }, + modifier = Modifier.fillMaxWidth(), + label = { Text("Trace (6) or RRN (12)") }, + singleLine = true + ) + + Spacer(modifier = Modifier.height(12.dp)) + + if (filtered.isEmpty()) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text("No transactions found.", color = Color.Gray) + } + } else { + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(filtered, key = { "${it.voucherNo}_${it.referNo}_${it.PID}" }) { payDetail -> + HistoryTransactionCard(payDetail) { + onReprintTransaction(prepareReprintPayDetail(payDetail)) + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/ReprintSettlementScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/ReprintSettlementScreen.kt new file mode 100644 index 0000000..267a744 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/ReprintSettlementScreen.kt @@ -0,0 +1,103 @@ +package com.mob.utsmyanmar.ui.history_functions + +import android.widget.Toast +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +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.core_utils.SystemParamsOperation + +@Composable +fun ReprintSettlementScreen( + viewModel: HistoryViewModel, + onBack: () -> Unit, + onDone: () -> Unit, +) { + val context = LocalContext.current + val voucherNo = SystemParamsOperation.getInstance().currentSerialNum + val settlements by viewModel.getLastSettlement(voucherNo).observeAsState(emptyList()) + var payDetail by remember { mutableStateOf(null) } + var hasStarted by remember { mutableStateOf(false) } + var printFailed by remember { mutableStateOf(false) } + + LaunchedEffect(settlements) { + if (hasStarted) return@LaunchedEffect + if (settlements.isEmpty()) { + Toast.makeText(context, "No transactions to print.", Toast.LENGTH_SHORT).show() + onBack() + return@LaunchedEffect + } + hasStarted = true + val detail = prepareReprintPayDetail(settlements.last()) + payDetail = detail + printSettlementReport(context, detail, onSuccess = onDone) { + printFailed = true + } + } + + Scaffold( + containerColor = Color.IvoryBeige, + topBar = { + AppBar(title = "Printing Settlement Report") + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + if (printFailed) { + Text("Printing settlement report failed.", color = Color.LegacyRed) + Spacer(modifier = Modifier.height(16.dp)) + Button( + onClick = { + payDetail?.let { detail -> + printSettlementReport(context, detail, onSuccess = onDone) { + printFailed = true + } + } + }, + colors = ButtonDefaults.buttonColors( + containerColor = Color.LegacyRed, + contentColor = Color.White + ) + ) { + Text("Retry") + } + Spacer(modifier = Modifier.height(8.dp)) + TextButton(onClick = onDone) { + Text("Done") + } + } else { + CircularProgressIndicator(color = Color.LegacyRed) + Spacer(modifier = Modifier.height(16.dp)) + Text("Printing settlement report...") + } + } + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/ReviewDetailScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/ReviewDetailScreen.kt new file mode 100644 index 0000000..911876b --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/ReviewDetailScreen.kt @@ -0,0 +1,54 @@ +package com.mob.utsmyanmar.ui.history_functions + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +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.unit.dp +import com.mob.utsmyanmar.ui.components.appbar.AppBar +import com.mob.utsmyanmar.ui.theme.Color + +@Composable +fun ReviewDetailScreen( + viewModel: HistoryViewModel, + trace: String, + onBack: () -> Unit, +) { + val transaction by viewModel.searchTransaction(trace).observeAsState() + + Scaffold( + containerColor = Color.IvoryBeige, + topBar = { + AppBar( + title = "Transaction Info", + icon = Icons.AutoMirrored.Filled.ArrowBack, + onIconClick = onBack + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(16.dp) + ) { + transaction?.let { payDetail -> + HistoryTransactionDetailCard(payDetail) + } ?: Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text("Transaction not found.", color = Color.Gray) + } + } + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/ReviewTraceScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/ReviewTraceScreen.kt new file mode 100644 index 0000000..e10ff19 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/ReviewTraceScreen.kt @@ -0,0 +1,142 @@ +package com.mob.utsmyanmar.ui.history_functions + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.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.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.LiveData +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 + +@Composable +fun ReviewTraceScreen( + viewModel: HistoryViewModel, + onBack: () -> Unit, + onNavigateReviewDetail: (String) -> Unit, +) { + var traceNumber by remember { mutableStateOf("") } + var searchedTrace by remember { mutableStateOf("") } + var lastNavigatedTrace by remember { mutableStateOf(null) } + + val transactionSource: LiveData = remember(searchedTrace) { + if (searchedTrace.isBlank()) { + MutableLiveData(null) + } else { + @Suppress("UNCHECKED_CAST") + viewModel.searchTransaction(searchedTrace) as LiveData + } + } + val transaction by transactionSource.observeAsState() + + LaunchedEffect(transaction?.voucherNo) { + val matchedTrace = transaction?.voucherNo ?: return@LaunchedEffect + if (lastNavigatedTrace == matchedTrace) return@LaunchedEffect + lastNavigatedTrace = matchedTrace + onNavigateReviewDetail(matchedTrace) + } + + Scaffold( + containerColor = Color.IvoryBeige, + topBar = { + AppBar( + title = "Review", + icon = Icons.AutoMirrored.Filled.ArrowBack, + onIconClick = onBack + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(16.dp) + ) { + Text( + text = if (searchedTrace.isNotBlank() && transaction == null) { + "No transaction found for trace $searchedTrace." + } else { + "Input trace number to review transaction." + }, + color = if (searchedTrace.isNotBlank() && transaction == null) { + Color.LegacyRed + } else { + Color.Black + }, + fontSize = 14.sp + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = Color.White) + ) { + Text( + text = traceNumber.padStart(6, '0').ifEmpty { "000000" }, + modifier = Modifier + .fillMaxWidth() + .padding(24.dp), + textAlign = TextAlign.Center, + fontSize = 28.sp, + fontWeight = FontWeight.Bold + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + HistoryNumericKeypad( + onDigit = { digit -> + if (traceNumber.length < 6) traceNumber += digit + }, + onBackspace = { + if (traceNumber.isNotEmpty()) { + traceNumber = traceNumber.dropLast(1) + } + }, + onClear = { traceNumber = "" } + ) + + Spacer(modifier = Modifier.weight(1f)) + + Button( + onClick = { searchedTrace = traceNumber.padStart(6, '0') }, + enabled = traceNumber.isNotBlank(), + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.LegacyRed, + contentColor = Color.White + ) + ) { + Text("Search") + } + } + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/SelectHostScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/SelectHostScreen.kt new file mode 100644 index 0000000..6275a0d --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/SelectHostScreen.kt @@ -0,0 +1,99 @@ +package com.mob.utsmyanmar.ui.history_functions + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +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.AlertDialog +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ElevatedButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +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.utils.core_utils.SystemParamsOperation +import com.utsmyanmar.paylibs.utils.enums.HostType + +@Composable +fun SelectHostScreen( + onBack: () -> Unit, + onSelectHost: (HostType) -> Unit, +) { + var unavailableMessage by remember { mutableStateOf(null) } + + Scaffold( + containerColor = Color.IvoryBeige, + topBar = { + AppBar( + title = "Select Host", + icon = Icons.AutoMirrored.Filled.ArrowBack, + onIconClick = onBack + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + HostButton("MPU") { onSelectHost(HostType.MPU) } + HostButton("Visa / Master") { + if (SystemParamsOperation.getInstance().isEmvEnabled) { + onSelectHost(HostType.VISA_MASTER) + } else { + unavailableMessage = "Currently this menu is unavailable!" + } + } + HostButton("QR Pay") { onSelectHost(HostType.QR) } + } + } + + unavailableMessage?.let { message -> + AlertDialog( + onDismissRequest = { unavailableMessage = null }, + confirmButton = { + TextButton(onClick = { unavailableMessage = null }) { + Text("OK") + } + }, + text = { Text(message) } + ) + } +} + +@Composable +private fun HostButton( + label: String, + onClick: () -> Unit, +) { + ElevatedButton( + onClick = onClick, + shape = RoundedCornerShape(12.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = Color.Black + ), + modifier = Modifier + .fillMaxWidth() + .height(64.dp) + ) { + Text(label, fontSize = 16.sp, fontWeight = FontWeight.SemiBold) + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/SummaryReportScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/SummaryReportScreen.kt new file mode 100644 index 0000000..1d65ae0 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/SummaryReportScreen.kt @@ -0,0 +1,220 @@ +package com.mob.utsmyanmar.ui.history_functions + +import android.app.DatePickerDialog +import android.app.TimePickerDialog +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +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.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.mob.utsmyanmar.ui.components.appbar.AppBar +import com.mob.utsmyanmar.ui.theme.Color +import com.utsmyanmar.paylibs.print.PrintHelper +import com.utsmyanmar.paylibs.print.printx.PrintXReceipt +import com.utsmyanmar.paylibs.print.printx.PrintXStatus +import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation +import com.utsmyanmar.paylibs.utils.enums.HostType +import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType +import com.utsmyanmar.paylibs.utils.params.Params +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date +import java.util.Locale + +@Composable +fun SummaryReportScreen( + viewModel: HistoryViewModel, + hostType: HostType, + onBack: () -> Unit, +) { + val context = LocalContext.current + val calendarStart = remember { Calendar.getInstance() } + val calendarEnd = remember { Calendar.getInstance() } + + var startDate by remember { mutableStateOf("") } + var endDate by remember { mutableStateOf("") } + var startHour by remember { mutableStateOf("") } + var startMinute by remember { mutableStateOf("") } + var endHour by remember { mutableStateOf("") } + var endMinute by remember { mutableStateOf("") } + var isPrinting by remember { mutableStateOf(false) } + var errorMessage by remember { mutableStateOf(null) } + + val allTransactions by viewModel.getSmileSummaryTrans( + SystemParamsOperation.getInstance().currentBatchNum + ).observeAsState(emptyList()) + + fun showDatePicker(isStart: Boolean) { + val calendar = if (isStart) calendarStart else calendarEnd + DatePickerDialog( + context, + { _, year, month, day -> + calendar.set(year, month, day) + val formatted = SimpleDateFormat("dd/MM/yy", Locale.getDefault()) + .format(calendar.time) + if (isStart) startDate = formatted else endDate = formatted + }, + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH) + ).show() + } + + fun showTimePicker(isStart: Boolean) { + val calendar = if (isStart) calendarStart else calendarEnd + TimePickerDialog( + context, + { _, hour, minute -> + calendar.set(Calendar.HOUR_OF_DAY, hour) + calendar.set(Calendar.MINUTE, minute) + val hr = String.format(Locale.getDefault(), "%02d", hour) + val min = String.format(Locale.getDefault(), "%02d", minute) + if (isStart) { + startHour = hr + startMinute = min + } else { + endHour = hr + endMinute = min + } + }, + calendar.get(Calendar.HOUR_OF_DAY), + calendar.get(Calendar.MINUTE), + false + ).show() + } + + fun printSummaryReport() { + if (startDate.isBlank() || endDate.isBlank() || + startHour.isBlank() || startMinute.isBlank() || + endHour.isBlank() || endMinute.isBlank() + ) { + errorMessage = "Please select date and time!" + return + } + + val pattern = "dd/MM/yy HH:mm" + val formatter = SimpleDateFormat(pattern, Locale.getDefault()) + val start: Date + val end: Date + try { + start = formatter.parse("$startDate $startHour:$startMinute") ?: return + end = formatter.parse("$endDate $endHour:$endMinute") ?: return + } catch (_: ParseException) { + errorMessage = "Invalid date or time." + return + } + + if (start.after(end)) { + errorMessage = "Invalid end date!" + return + } + + val settleData = buildSummarySettleData(allTransactions, hostType, start, end) + if (settleData.saleCount + settleData.wavePayCount + + settleData.voidSaleCount + settleData.cashAdvanceCount + + settleData.preAuthCount + settleData.refundCount + settleData.waveRefundCount == 0 + ) { + errorMessage = "No transactions to print." + return + } + + if (PrintHelper.getInstance().paperRollStatus() != 1) { + errorMessage = "Paper roll not ready." + return + } + + val tradeData = Params.newTrade(false) + val payDetail = tradeData.payDetail + payDetail.settleDataObj = settleData + payDetail.transType = TransactionsType.SUMMARY_REPORT.name + payDetail.transactionType = TransactionsType.SUMMARY_REPORT.value + payDetail.accountType = hostTypeAccountLabel(hostType) + + isPrinting = true + PrintXReceipt.getInstance().printSmileSummaryReport( + payDetail, + object : PrintXStatus { + override fun onSuccess() { + isPrinting = false + } + + override fun onFailure() { + isPrinting = false + errorMessage = "Print failed." + } + } + ) + } + + Scaffold( + containerColor = Color.IvoryBeige, + topBar = { + AppBar( + title = "Transaction Summary", + icon = Icons.AutoMirrored.Filled.ArrowBack, + onIconClick = onBack + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + SummaryPickerRow("Start Date", startDate.ifBlank { "Select" }) { showDatePicker(true) } + SummaryPickerRow("Start Time", formatTime(startHour, startMinute)) { showTimePicker(true) } + SummaryPickerRow("End Date", endDate.ifBlank { "Select" }) { showDatePicker(false) } + SummaryPickerRow("End Time", formatTime(endHour, endMinute)) { showTimePicker(false) } + + Spacer(modifier = Modifier.weight(1f)) + + Button( + onClick = ::printSummaryReport, + enabled = !isPrinting, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.LegacyRed, + contentColor = Color.White + ) + ) { + Text(if (isPrinting) "Printing..." else "Print") + } + } + } + + errorMessage?.let { message -> + AlertDialog( + onDismissRequest = { errorMessage = null }, + confirmButton = { + TextButton(onClick = { errorMessage = null }) { + Text("OK") + } + }, + text = { Text(message) } + ) + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/TransactionNotFoundScreen.kt b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/TransactionNotFoundScreen.kt new file mode 100644 index 0000000..ab47ef3 --- /dev/null +++ b/app/src/main/java/com/mob/utsmyanmar/ui/history_functions/TransactionNotFoundScreen.kt @@ -0,0 +1,42 @@ +package com.mob.utsmyanmar.ui.history_functions + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.sp +import com.mob.utsmyanmar.ui.components.appbar.AppBar +import com.mob.utsmyanmar.ui.theme.Color + +@Composable +fun TransactionNotFoundScreen(onBack: () -> Unit) { + Scaffold( + containerColor = Color.IvoryBeige, + topBar = { + AppBar( + title = "Transaction Info", + icon = Icons.AutoMirrored.Filled.ArrowBack, + onIconClick = onBack + ) + } + ) { paddingValues -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + contentAlignment = Alignment.Center + ) { + Text( + text = "Transaction not found.", + color = Color.Gray, + fontSize = 16.sp + ) + } + } +} diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt index 5b07eaa..220364b 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/AppNavGraph.kt @@ -22,6 +22,7 @@ import com.mob.utsmyanmar.ui.dashboard.DashboardViewModel import com.mob.utsmyanmar.ui.device_info.DeviceInfoViewModel import com.mob.utsmyanmar.ui.disable.DisableScreen import com.mob.utsmyanmar.ui.functions.FunctionsScreen +import com.mob.utsmyanmar.ui.history_functions.HistoryRoute import com.mob.utsmyanmar.ui.input_amount.AmountRoute import com.mob.utsmyanmar.ui.manual_entry.InputManualCardRoute import com.mob.utsmyanmar.ui.notification.NotificationDetailScreen @@ -158,6 +159,11 @@ fun AppNavGraph( launchSingleTop = true } }, + onNavigateHistory = { + navController.navigate(Routes.Password.createRoute(Routes.History.route, PasswordType.SYSTEM)) { + launchSingleTop = true + } + }, onNavigateAction = { action -> when (action) { "Void" -> { @@ -225,6 +231,24 @@ fun AppNavGraph( ) } + composable(Routes.History.route) { + val sharedViewModel: SharedViewModel = hiltViewModel(activity) + val transResultViewModel: TransactionResultViewModel = hiltViewModel(activity) + HistoryRoute( + sharedViewModel = sharedViewModel, + onBack = { navController.popBackStack() }, + onNavigatePrintReceipt = { + transResultViewModel.onEvent( + TransactionResultEvent.Start, + sharedViewModel + ) + navController.navigate(Routes.PrintReceipt.route) { + launchSingleTop = true + } + } + ) + } + composable(Routes.VoidTrace.route) { val voidViewModel: VoidViewModel = hiltViewModel() val sharedViewModel: SharedViewModel = hiltViewModel(activity) diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt index 51cccb7..fc05680 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/navigation/Routes.kt @@ -40,6 +40,7 @@ sealed class Routes(val route: String) { data object NotificationDetail : Routes("notification_detail/{notificationId}") { fun createRoute(id: Int): String = "notification_detail/$id" } + data object History : Routes("history") data object Disable : Routes("disable/{message}") { fun createRoute(message: String): String = "disable/${Uri.encode(message)}" } diff --git a/app/src/main/java/com/mob/utsmyanmar/ui/sending_to_host/SendingToHostRoute.kt b/app/src/main/java/com/mob/utsmyanmar/ui/sending_to_host/SendingToHostRoute.kt index fc82ead..f3ce5a9 100644 --- a/app/src/main/java/com/mob/utsmyanmar/ui/sending_to_host/SendingToHostRoute.kt +++ b/app/src/main/java/com/mob/utsmyanmar/ui/sending_to_host/SendingToHostRoute.kt @@ -1,9 +1,5 @@ package com.mob.utsmyanmar.ui.sending_to_host -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -12,19 +8,12 @@ import com.mob.utsmyanmar.viewmodel.SharedViewModel import com.mob.utsmyanmar.viewmodel.TransProcessViewModel import kotlinx.coroutines.delay import androidx.compose.runtime.getValue -import com.mob.utsmyanmar.ui.transaction_result.TransactionResultState -import com.mob.utsmyanmar.viewmodel.ProcessingTransaction -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Modifier import com.mob.utsmyanmar.model.TransactionStatus import com.mob.utsmyanmar.ui.settlement.SettlementViewModel import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType -import kotlinx.coroutines.channels.Channel private const val MOCK_HOST_DELAY_MS = 2000L @@ -43,8 +32,6 @@ fun ProcessingRoute( val settlementStatus by settlementViewModel.uiState.collectAsStateWithLifecycle() - var showReversalDialog by rememberSaveable { mutableStateOf(false) } - var reversalMessage by rememberSaveable { mutableStateOf("") } if(sharedViewModel.transactionsType.value == TransactionsType.SETTLEMENT) { LaunchedEffect(Unit) {