added history functions

This commit is contained in:
kizzy 2026-06-26 01:35:50 +07:00
parent f827e75b1e
commit 1a7e4c2020
20 changed files with 1812 additions and 14 deletions

View File

@ -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<DashboardMenuItem> = listOf(
DashboardMenuItem(
@ -705,7 +708,7 @@ private fun buildMenuItems(
modifier = Modifier.size(32.dp),
tint = Color.LegacyRed
)
}) { },
}) { onNavigateHistory() },
DashboardMenuItem(
"Sign On",
{

View File

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

View File

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

View File

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

View File

@ -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<PayDetail>,
hostType: HostType
): List<PayDetail> {
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<PayDetail>,
hostType: HostType
): List<PayDetail> {
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<PayDetail>,
query: String
): List<PayDetail> {
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
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<List<PayDetail>> =
repository.getTransactionHistory()
fun getLastTransaction(voucherNo: String): LiveData<List<PayDetail>> =
repository.getLastTransaction(voucherNo)
fun getLastSettlement(voucherNo: String): LiveData<List<PayDetail>> =
repository.getLastSettlement(voucherNo)
fun getReviewBatchTransaction(batchNo: String): LiveData<List<PayDetail>> =
repository.getReviewBatchTransaction(batchNo)
fun getSmileSummaryTrans(batchNo: String): LiveData<List<PayDetail>> =
repository.getSmileSummaryTrans(batchNo)
fun searchTransaction(voucherNo: String): LiveData<PayDetail> =
repository.searchTransaction(voucherNo)
}

View File

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

View File

@ -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<PayDetail?>(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...")
}
}
}
}

View File

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

View File

@ -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<String?>(null) }
val transactionSource: LiveData<PayDetail?> = remember(searchedTrace) {
if (searchedTrace.isBlank()) {
MutableLiveData(null)
} else {
@Suppress("UNCHECKED_CAST")
viewModel.searchTransaction(searchedTrace) as LiveData<PayDetail?>
}
}
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")
}
}
}
}

View File

@ -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<String?>(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)
}
}

View File

@ -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<String?>(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) }
)
}
}

View File

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

View File

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

View File

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

View File

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