Compare commits

...

2 Commits

Author SHA1 Message Date
moon
907eab4e72 fixed mini errors 2026-09-16 17:12:48 +06:30
moon
d239da7e72 settlement double print fixed 2026-09-16 16:17:51 +06:30
16 changed files with 113 additions and 48 deletions

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="jbr-21" project-jdk-type="JavaSDK" /> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK" />
</project> </project>

View File

@ -40,8 +40,8 @@ android {
dimension = "env" dimension = "env"
applicationIdSuffix = ".uat" applicationIdSuffix = ".uat"
versionNameSuffix = "-UAT" versionNameSuffix = "-UAT"
versionCode = 19 versionCode = 21
versionName = "3.7" versionName = "3.9"
buildConfigField("String", "ENV", "\"UAT\"") buildConfigField("String", "ENV", "\"UAT\"")
resValue("string", "app_name", "MOB-UAT") resValue("string", "app_name", "MOB-UAT")
} }

View File

@ -33,4 +33,19 @@
* qr for user manual * qr for user manual
### version 3.7 ### version 3.7
* auto-settlement for both P2 and P3 * auto-settlement for both P2 and P3
### version 3.8
* Slip design modified
* Continue Button hide in transactions success
* date format 2026 aligned in paper slip
* fixed 2 paper roll out after settlement
* fixed terminal disable/enable screen shaking
* cardholder name adjust to 20 words
* card masked in PrintPreview Screen
* cash adv in settlement
### version 3.9
* modified date/time in settlement status in home
* fixed settlement status not updating
* updated released date

View File

@ -10,7 +10,7 @@ import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
import java.util.Calendar import java.util.Calendar
object SettlementScheduler { object SettlementScheduler {
private const val FALLBACK_ALERT_TIME = "23:30:00" private const val FALLBACK_ALERT_TIME = "23:00:00"
private const val REQUEST_CODE = 100 private const val REQUEST_CODE = 100
fun schedule(context: Context): Boolean { fun schedule(context: Context): Boolean {

View File

@ -267,7 +267,7 @@ fun DashboardScreen2(
) )
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
Text( Text(
text = "Released on : 06-Sep-2026", text = "Released on : 16-Sep-2026",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = Color.Gray, color = Color.Gray,
modifier = Modifier.padding(horizontal = 16.dp) modifier = Modifier.padding(horizontal = 16.dp)

View File

@ -17,7 +17,7 @@ data class DashboardUiState(
val totalSaleCount: Int = 0, val totalSaleCount: Int = 0,
val totalSaleAmount: Long = 0L, val totalSaleAmount: Long = 0L,
val settlementStatusText: String = "Pending", val settlementStatusText: String = "Pending",
val settlementDateTimeText: String = "--/-- --:--", val settlementDateTimeText: String = "--/--/----\n--:--:--",
val isSyncing: Boolean = false, val isSyncing: Boolean = false,
val syncStatusText: String = "Connecting to TMS server..." val syncStatusText: String = "Connecting to TMS server..."
) )

View File

@ -18,7 +18,6 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.math.BigDecimal import java.math.BigDecimal
import java.text.DecimalFormat import java.text.DecimalFormat
import java.util.Calendar
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -33,7 +32,7 @@ class DashboardViewModel @Inject constructor(
init { init {
viewModelScope.launch { viewModelScope.launch {
repository.getLastTenTransactions().asFlow().collect { payDetails -> repository.getLastTenTransactions().asFlow().collect { payDetails ->
val latestSettlement = payDetails.lastOrNull { val latestSettlement = payDetails.firstOrNull {
it.transactionType == TransactionsType.SETTLEMENT.value it.transactionType == TransactionsType.SETTLEMENT.value
} }
val isCurrentBusinessSettlement = latestSettlement?.isCurrentBusinessSettlement() == true val isCurrentBusinessSettlement = latestSettlement?.isCurrentBusinessSettlement() == true
@ -172,22 +171,14 @@ private fun formatTypeLabel(raw: String): String = when {
// TradeDate = "MMDD", TradeTime = "HHmmss" (ISO 8583 fields 13 & 12) // TradeDate = "MMDD", TradeTime = "HHmmss" (ISO 8583 fields 13 & 12)
private fun buildDateTime(date: String?, time: String?): String { private fun buildDateTime(date: String?, time: String?): String {
val d = date?.padStart(4, '0')?.takeIf { it.length >= 4 } ?: return "--/-- --:--" val d = date?.padStart(4, '0')?.takeIf { it.length >= 4 }
?: return "--/--/----\n--:--:--"
val t = (time ?: "").padStart(6, '0') val t = (time ?: "").padStart(6, '0')
return "${d.take(2)}/${d.drop(2).take(2)} ${t.take(2)}:${t.drop(2).take(2)}" return "${d.drop(2).take(2)}/${d.take(2)}/${SystemDateTime.getYYYY()}\n" +
"${t.take(2)}:${t.drop(2).take(2)}:${t.drop(4).take(2)}"
} }
private fun PayDetail.isCurrentBusinessSettlement(): Boolean { private fun PayDetail.isCurrentBusinessSettlement(): Boolean {
if (transactionType != TransactionsType.SETTLEMENT.value) return false return transactionType == TransactionsType.SETTLEMENT.value &&
if (TradeDate != SystemDateTime.getMMDD()) return false TradeDate == SystemDateTime.getMMDD()
val now = Calendar.getInstance()
val cutoff = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 23)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
return now.before(cutoff)
} }

View File

@ -1,6 +1,7 @@
package com.mob.utsmyanmar.ui.history_functions package com.mob.utsmyanmar.ui.history_functions
import com.utsmyanmar.paylibs.model.PayDetail import com.utsmyanmar.paylibs.model.PayDetail
import com.utsmyanmar.paylibs.model.SettleData
import com.utsmyanmar.paylibs.utils.POSUtil import com.utsmyanmar.paylibs.utils.POSUtil
import com.utsmyanmar.paylibs.utils.enums.HostType import com.utsmyanmar.paylibs.utils.enums.HostType
import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType import com.utsmyanmar.paylibs.utils.iso_utils.TransactionsType
@ -17,6 +18,10 @@ enum class HistoryHostPurpose {
} }
internal fun prepareReprintPayDetail(payDetail: PayDetail): PayDetail { internal fun prepareReprintPayDetail(payDetail: PayDetail): PayDetail {
if (payDetail.settleDataObj == null) {
payDetail.settleDataObj = parseSettlementData(payDetail.settleList)
}
val suffix = "(REPRINT)" val suffix = "(REPRINT)"
payDetail.transType = if (payDetail.transType.length >= 20) { payDetail.transType = if (payDetail.transType.length >= 20) {
"${payDetail.transType}\n$suffix" "${payDetail.transType}\n$suffix"
@ -26,6 +31,26 @@ internal fun prepareReprintPayDetail(payDetail: PayDetail): PayDetail {
return payDetail return payDetail
} }
private fun parseSettlementData(value: String): SettleData? {
val parts = value.split("-")
if (parts.size < 4) return null
fun count(index: Int): Int = parts[index]
.substringBefore(":")
.toIntOrNull() ?: 0
fun amount(index: Int): Long = parts[index]
.substringAfter(":")
.toLongOrNull() ?: 0L
return SettleData(
count(0), amount(0),
count(3), amount(3),
count(2), amount(2),
count(1), amount(1)
)
}
internal fun displayTransactionStatus(payDetail: PayDetail): String { internal fun displayTransactionStatus(payDetail: PayDetail): String {
if (payDetail.transactionType == TransactionsType.REVERSAL.value) { if (payDetail.transactionType == TransactionsType.REVERSAL.value) {
return if (payDetail.isNeedReversal) "REVERSAL FAILED" else "REVERSAL SUCCESS" return if (payDetail.isNeedReversal) "REVERSAL FAILED" else "REVERSAL SUCCESS"

View File

@ -18,8 +18,8 @@ class HistoryViewModel @Inject constructor(
fun getLastTransaction(voucherNo: String): LiveData<List<PayDetail>> = fun getLastTransaction(voucherNo: String): LiveData<List<PayDetail>> =
repository.getLastTransaction(voucherNo) repository.getLastTransaction(voucherNo)
fun getLastSettlement(voucherNo: String): LiveData<List<PayDetail>> = fun getLastSettlement(): LiveData<List<PayDetail>> =
repository.getLastSettlement(voucherNo) repository.getLastSettlement()
fun getReviewBatchTransaction(batchNo: String): LiveData<List<PayDetail>> = fun getReviewBatchTransaction(batchNo: String): LiveData<List<PayDetail>> =
repository.getReviewBatchTransaction(batchNo) repository.getReviewBatchTransaction(batchNo)

View File

@ -27,7 +27,6 @@ import androidx.compose.ui.unit.dp
import com.mob.utsmyanmar.ui.components.appbar.AppBar import com.mob.utsmyanmar.ui.components.appbar.AppBar
import com.mob.utsmyanmar.ui.theme.Color import com.mob.utsmyanmar.ui.theme.Color
import com.utsmyanmar.paylibs.model.PayDetail import com.utsmyanmar.paylibs.model.PayDetail
import com.utsmyanmar.paylibs.utils.core_utils.SystemParamsOperation
@Composable @Composable
fun ReprintSettlementScreen( fun ReprintSettlementScreen(
@ -36,27 +35,43 @@ fun ReprintSettlementScreen(
onDone: () -> Unit, onDone: () -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val voucherNo = SystemParamsOperation.getInstance().currentSerialNum val settlements by viewModel.getLastSettlement().observeAsState()
val settlements by viewModel.getLastSettlement(voucherNo).observeAsState(emptyList())
var payDetail by remember { mutableStateOf<PayDetail?>(null) } var payDetail by remember { mutableStateOf<PayDetail?>(null) }
var hasStarted by remember { mutableStateOf(false) } var hasStarted by remember { mutableStateOf(false) }
var printFailed by remember { mutableStateOf(false) } var printFailed by remember { mutableStateOf(false) }
var printFinished by remember { mutableStateOf(false) }
LaunchedEffect(settlements) { LaunchedEffect(settlements) {
if (hasStarted) return@LaunchedEffect if (hasStarted) return@LaunchedEffect
if (settlements.isEmpty()) { val records = settlements ?: return@LaunchedEffect
if (records.isEmpty()) {
Toast.makeText(context, "No transactions to print.", Toast.LENGTH_SHORT).show() Toast.makeText(context, "No transactions to print.", Toast.LENGTH_SHORT).show()
onBack() onBack()
return@LaunchedEffect return@LaunchedEffect
} }
hasStarted = true hasStarted = true
val detail = prepareReprintPayDetail(settlements.last()) val detail = prepareReprintPayDetail(records.first())
payDetail = detail payDetail = detail
printSettlementReport(context, detail, onSuccess = onDone) { printSettlementReport(context, detail, onSuccess = {
printFinished = true
}) {
printFailed = true printFailed = true
} }
} }
LaunchedEffect(printFinished) {
if (printFinished) onDone()
}
LaunchedEffect(hasStarted, printFailed, printFinished) {
if (!hasStarted || printFailed || printFinished) return@LaunchedEffect
// Some Sunmi firmware prints successfully without sending onPrintResult.
kotlinx.coroutines.delay(8_000)
if (!printFailed && !printFinished) {
printFinished = true
}
}
Scaffold( Scaffold(
containerColor = Color.IvoryBeige, containerColor = Color.IvoryBeige,
topBar = { topBar = {
@ -77,7 +92,11 @@ fun ReprintSettlementScreen(
Button( Button(
onClick = { onClick = {
payDetail?.let { detail -> payDetail?.let { detail ->
printSettlementReport(context, detail, onSuccess = onDone) { printFailed = false
printFinished = false
printSettlementReport(context, detail, onSuccess = {
printFinished = true
}) {
printFailed = true printFailed = true
} }
} }

View File

@ -36,15 +36,21 @@ fun ProcessingRoute(
settlementViewModel.startSettlementProcess() settlementViewModel.startSettlementProcess()
} }
LaunchedEffect(settlementStatus.status) { LaunchedEffect(Unit) {
when(settlementStatus.status) { settlementViewModel.events.collect { event ->
TransactionStatus.ON_ERROR, when (event) {
TransactionStatus.ON_SUCCESS -> { is com.mob.utsmyanmar.ui.settlement.SettlementEvent.ShowStatus -> {
onTransactionFinished() when (event.status) {
sharedViewModel.payDetail.value = settlementViewModel.getPayDetail() TransactionStatus.ON_ERROR,
onNavigateTransactionResult() TransactionStatus.ON_SUCCESS -> {
onTransactionFinished()
sharedViewModel.payDetail.value = settlementViewModel.getPayDetail()
onNavigateTransactionResult()
}
else -> Unit
}
}
} }
else -> {}
} }
} }
} else { } else {

View File

@ -111,8 +111,8 @@ class SettlementViewModel @Inject constructor(
private val _events = Channel<SettlementEvent>(Channel.BUFFERED) private val _events = Channel<SettlementEvent>(Channel.BUFFERED)
val events = _events.receiveAsFlow() val events = _events.receiveAsFlow()
fun getLastSettlement(voucherNo: String): LiveData<List<PayDetail>> { fun getLastSettlement(): LiveData<List<PayDetail>> {
return repository.getLastSettlement(voucherNo) return repository.getLastSettlement()
} }
fun getSettlement(): LiveData<List<PayDetail>> { fun getSettlement(): LiveData<List<PayDetail>> {
@ -253,7 +253,10 @@ class SettlementViewModel @Inject constructor(
fun startSettlementProcess() { fun startSettlementProcess() {
_uiState.update { _uiState.update {
it.copy(isLoading = true) it.copy(
status = null,
isLoading = true
)
} }
SystemParamsOperation.getInstance().getIncrementBatchNo() SystemParamsOperation.getInstance().getIncrementBatchNo()
@ -466,6 +469,7 @@ class SettlementViewModel @Inject constructor(
if (!errorFlag && settlementApproved) { if (!errorFlag && settlementApproved) {
updateDB() updateDB()
insertPayDetail(currentPayDetail) insertPayDetail(currentPayDetail)
SystemParamsOperation.getInstance().setNeedSettlement(false)
postStatus(TransactionStatus.ON_SUCCESS) postStatus(TransactionStatus.ON_SUCCESS)
} else { } else {
postStatus(TransactionStatus.ON_ERROR) postStatus(TransactionStatus.ON_ERROR)

View File

@ -128,8 +128,8 @@ public interface PayDetailDao {
@Query(("SELECT * FROM paydetail WHERE transactionType = 3 AND voucherNo IN(:voucherNo)")) @Query(("SELECT * FROM paydetail WHERE transactionType = 3 AND voucherNo IN(:voucherNo)"))
LiveData<PayDetail> checkLastSettlement(String voucherNo); LiveData<PayDetail> checkLastSettlement(String voucherNo);
@Query("SELECT * FROM paydetail WHERE transactionType = 3 AND voucherNo <= :voucherNo AND accountType != 'MPU'") @Query("SELECT * FROM paydetail WHERE transactionType = 3 ORDER BY PID DESC")
LiveData<List<PayDetail>> getLastSettlement(String voucherNo); LiveData<List<PayDetail>> getLastSettlement();
// @Query("SELECT * FROM paydetail WHERE transactionType != 10 AND voucherNo <= :voucherNo") // @Query("SELECT * FROM paydetail WHERE transactionType != 10 AND voucherNo <= :voucherNo")
@Query("SELECT * FROM paydetail WHERE transactionType != 10 AND voucherNo IN(:voucherNo)") @Query("SELECT * FROM paydetail WHERE transactionType != 10 AND voucherNo IN(:voucherNo)")

View File

@ -99,7 +99,7 @@ public class Repository {
public LiveData<List<PayDetail>> getTransactionHistory(){ return payDetailDao.getTransactionHistory();} public LiveData<List<PayDetail>> getTransactionHistory(){ return payDetailDao.getTransactionHistory();}
public LiveData<List<PayDetail>> getLastSettlement(String voucherNo) { return payDetailDao.getLastSettlement(voucherNo);} public LiveData<List<PayDetail>> getLastSettlement() { return payDetailDao.getLastSettlement();}
public LiveData<List<PayDetail>> getLastTransaction(String voucherNo) { return payDetailDao.getLastTransaction(voucherNo);} public LiveData<List<PayDetail>> getLastTransaction(String voucherNo) { return payDetailDao.getLastTransaction(voucherNo);}

View File

@ -199,6 +199,11 @@ public class PrintXImpl extends BaseXPrint implements PrintX {
this.callbackStatus = printXStatus; this.callbackStatus = printXStatus;
try { try {
if (payDetail == null || payDetail.getSettleDataObj() == null) {
printXStatus.onFailure();
return;
}
LogUtil.d(TAG, payDetail.getSettleDataObj().toString()); LogUtil.d(TAG, payDetail.getSettleDataObj().toString());
setHeight(0x12); setHeight(0x12);

View File

@ -38,8 +38,8 @@ public class SystemParamsSettings implements Serializable {
private boolean checkExpSwitch = false; private boolean checkExpSwitch = false;
// private String tmsAddress = "https://posmgmt.myanmarorientalbank.com/api/v1"; private String tmsAddress = "https://posmgmt.myanmarorientalbank.com/api/v1";
private String tmsAddress = "https://sirius-nest.utsmyanmar.com/api/v1"; // private String tmsAddress = "https://sirius-nest.utsmyanmar.com/api/v1";
// private String tmsAddress = "http://128.199.170.203"; // private String tmsAddress = "http://128.199.170.203";
private String terminalCapability = "E0E8C8"; private String terminalCapability = "E0E8C8";