Compare commits

..

No commits in common. "83047648b4e09d7654480d7aa688b9a87515f3f0" and "70f2913989cef72d3a72f3bce8ef43e0f47e4b70" have entirely different histories.

16 changed files with 101 additions and 146 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@
.cxx .cxx
local.properties local.properties
/.idea /.idea
/.idea
/sunmiui-lib /sunmiui-lib
/paysdk-lib /paysdk-lib
/app/uat /app/uat

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -4,10 +4,10 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-09-15T06:58:26.019801800Z"> <DropdownSelection timestamp="2026-05-07T16:25:07.233027800Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=PB13216J53652" /> <DeviceId pluginId="PhysicalDevice" identifier="serial=PB0219AQ60954" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>

View File

@ -0,0 +1,62 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="UsePropertyAccessSyntax" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@ -1,5 +1,10 @@
<?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" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK" /> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project> </project>

View File

@ -39,9 +39,7 @@ class DashboardViewModel @Inject constructor(
val isCurrentBusinessSettlement = latestSettlement?.isCurrentBusinessSettlement() == true val isCurrentBusinessSettlement = latestSettlement?.isCurrentBusinessSettlement() == true
_uiState.update { _uiState.update {
it.copy( it.copy(
recentTransactions = payDetails recentTransactions = payDetails.map { pd -> pd.toRecord() },
.filterNot { it.isSettlementTransaction() }
.map { pd -> pd.toRecord() },
totalSaleCount = payDetails.totalSaleCount(), totalSaleCount = payDetails.totalSaleCount(),
totalSaleAmount = payDetails.totalSaleAmount(), totalSaleAmount = payDetails.totalSaleAmount(),
settlementStatusText = if (isCurrentBusinessSettlement) { settlementStatusText = if (isCurrentBusinessSettlement) {
@ -153,11 +151,6 @@ private fun PayDetail.isSaleTransaction(): Boolean {
(transactionType == TransactionsType.WAVEPAY.value && qrTransStatus == 1) (transactionType == TransactionsType.WAVEPAY.value && qrTransStatus == 1)
} }
private fun PayDetail.isSettlementTransaction(): Boolean {
return transactionType == TransactionsType.SETTLEMENT.value ||
transType?.contains("SETTLEMENT", ignoreCase = true) == true
}
private fun formatTypeLabel(raw: String): String = when { private fun formatTypeLabel(raw: String): String = when {
raw.equals("CASH_OUT", ignoreCase = true) -> "CASH_ADV" raw.equals("CASH_OUT", ignoreCase = true) -> "CASH_ADV"
raw.contains("SALE", ignoreCase = true) -> "Sale" raw.contains("SALE", ignoreCase = true) -> "Sale"

View File

@ -2,7 +2,6 @@ package com.mob.utsmyanmar.ui.navigation
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Build import android.os.Build
import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -168,7 +167,6 @@ fun AppNavGraph(
LaunchedEffect(tmsSetupUiState.isTerminalDisabled) { LaunchedEffect(tmsSetupUiState.isTerminalDisabled) {
if (tmsSetupUiState.isTerminalDisabled) { if (tmsSetupUiState.isTerminalDisabled) {
tmsSetupViewModel.clearTerminalDisabledState()
navController.navigate(Routes.TmsSetup.route) { navController.navigate(Routes.TmsSetup.route) {
popUpTo(Routes.Dashboard.route) { popUpTo(Routes.Dashboard.route) {
inclusive = true inclusive = true
@ -844,9 +842,7 @@ fun AppNavGraph(
}, },
onShowError = {}, onShowError = {},
onShowSuccess = {}, onShowSuccess = {},
onShowPrinterDialog = { message -> onShowPrinterDialog = {}
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
}
) )
} }

View File

@ -82,7 +82,6 @@ class SettlementViewModel @Inject constructor(
private var flag = false private var flag = false
private var errorFlag = false private var errorFlag = false
private var settlementApproved = false
private var isSecondCall = false private var isSecondCall = false
private var batchIndex = 0 private var batchIndex = 0
@ -240,8 +239,6 @@ class SettlementViewModel @Inject constructor(
repository.deletePayDetail(pay) repository.deletePayDetail(pay)
} }
repository.deleteReversalTransactions()
deleteTrans.forEach { pay -> deleteTrans.forEach { pay ->
repository.deletePayDetail(pay) repository.deletePayDetail(pay)
} }
@ -272,10 +269,6 @@ class SettlementViewModel @Inject constructor(
private fun requestOnlineProcessSettlement() { private fun requestOnlineProcessSettlement() {
val state = _uiState.value val state = _uiState.value
// Keep the local batch until this request receives an approved response.
settlementApproved = false
errorFlag = false
val hostName = HostName.MPU val hostName = HostName.MPU
val field60 = SystemParamsOperation.getInstance().getCurrentBatchNum() val field60 = SystemParamsOperation.getInstance().getCurrentBatchNum()
@ -414,23 +407,15 @@ class SettlementViewModel @Inject constructor(
when { when {
resultStr == Constant.ANSWER_CODE_ACCEPT || resultStr == Constant.ANSWER_CODE_ACCEPT ||
resultStr == Constant.ANSWER_CODE_APPROVED -> { resultStr == Constant.ANSWER_CODE_APPROVED -> {
settlementApproved = true
currentPayDetail.isNeedReversal = false currentPayDetail.isNeedReversal = false
} }
resultStr == "95" || resultStr == "095" -> { resultStr == "95" || resultStr == "095" -> {
settlementApproved = false
currentPayDetail.isNeedReversal = !flag currentPayDetail.isNeedReversal = !flag
} }
else -> {
settlementApproved = false
currentPayDetail.isNeedReversal = false
}
} }
} else { } else {
errorFlag = true errorFlag = true
settlementApproved = false
} }
} }
@ -462,12 +447,13 @@ class SettlementViewModel @Inject constructor(
flag = false flag = false
batchIndex = 0 batchIndex = 0
if (!errorFlag && settlementApproved) { updateDB()
updateDB() insertPayDetail(currentPayDetail)
insertPayDetail(currentPayDetail)
postStatus(TransactionStatus.ON_SUCCESS) if (errorFlag) {
} else {
postStatus(TransactionStatus.ON_ERROR) postStatus(TransactionStatus.ON_ERROR)
} else {
postStatus(TransactionStatus.ON_SUCCESS)
} }
_uiState.update { _uiState.update {

View File

@ -44,10 +44,6 @@ fun TmsSetupRoute(
) { ) {
val state by viewModel.uiState.collectAsState() val state by viewModel.uiState.collectAsState()
LaunchedEffect(viewModel) {
viewModel.initialize()
}
LaunchedEffect(viewModel) { LaunchedEffect(viewModel) {
viewModel.navigateToDashboard.collect { onNavigateDashboard() } viewModel.navigateToDashboard.collect { onNavigateDashboard() }
} }

View File

@ -49,7 +49,7 @@ class TmsSetupViewModel @Inject constructor(
private val emvParamOperation: EmvParamOperation, private val emvParamOperation: EmvParamOperation,
@ApplicationContext private val context: Context @ApplicationContext private val context: Context
) : ViewModel() { ) : ViewModel() {
private val TAG = TmsSetupViewModel::class.simpleName private val TAG = TmsSetupViewModel::class.simpleName;
private val _uiState = MutableStateFlow(TmsSetupUiState()) private val _uiState = MutableStateFlow(TmsSetupUiState())
private val _dashboardUiState = MutableStateFlow(DashboardUiState()) private val _dashboardUiState = MutableStateFlow(DashboardUiState())
val uiState: StateFlow<TmsSetupUiState> = _uiState.asStateFlow() val uiState: StateFlow<TmsSetupUiState> = _uiState.asStateFlow()
@ -65,14 +65,8 @@ class TmsSetupViewModel @Inject constructor(
private val disposables = CompositeDisposable() private val disposables = CompositeDisposable()
private val tmsSetups = TMSSetupsImpl() private val tmsSetups = TMSSetupsImpl()
private var requestInProgress = false
private var initialized = false
fun initialize() {
if (initialized) return
initialized = true
init {
viewModelScope.launch { viewModelScope.launch {
waitForHardware() waitForHardware()
TMSUtil.getInstance().generateFinalVersion() TMSUtil.getInstance().generateFinalVersion()
@ -81,29 +75,20 @@ class TmsSetupViewModel @Inject constructor(
} }
fun downloadConfigs(onSuccess: () -> Unit = {}) { fun downloadConfigs(onSuccess: () -> Unit = {}) {
// Do not use isLoading here because it also covers the hardware startup phase.
if (requestInProgress) return
requestInProgress = true
_uiState.update { _uiState.update {
it.copy( it.copy(isLoading = true, isError = false, statusText = "Connecting to TMS server...")
isLoading = true,
isError = false,
isTerminalDisabled = false,
statusText = "Connecting to TMS server..."
)
} }
val disposable = repository.getParams(buildRequest()) val disposable = repository.getParams(buildRequest())
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.doFinally { requestInProgress = false }
.subscribe( .subscribe(
{ response -> { response ->
_uiState.update { it.copy( _uiState.update { it.copy(
statusText = "Applying configuration...") statusText = "Applying configuration...")
} }
val appResponse = SiriusResponse( val appResponse = SiriusResponse(
serial = response.serial.orEmpty(), serial = response.serial.orEmpty(),
ecrKey = response.ecrKey.orEmpty(), ecrKey = response.ecrKey.orEmpty(),
@ -126,6 +111,7 @@ class TmsSetupViewModel @Inject constructor(
"Empty error response (${error.code()})" "Empty error response (${error.code()})"
} }
} catch (e: Exception) { } catch (e: Exception) {
// Server returned non-JSON (HTML 404 page, plain text, etc.)
"Server error: ${error.code()}" "Server error: ${error.code()}"
} }
} else { } else {
@ -152,6 +138,7 @@ class TmsSetupViewModel @Inject constructor(
delay(500.milliseconds) delay(500.milliseconds)
elapsed += 500 elapsed += 500
} }
} }
private fun onConfigApplied(onSuccess: () -> Unit) { private fun onConfigApplied(onSuccess: () -> Unit) {
@ -161,7 +148,6 @@ class TmsSetupViewModel @Inject constructor(
LogUtil.d(TAG, "isActive ${ops.isActive}") LogUtil.d(TAG, "isActive ${ops.isActive}")
val msg = ops.disabledMsg.takeIf { it.isNotEmpty() } val msg = ops.disabledMsg.takeIf { it.isNotEmpty() }
?: "This terminal has been disabled. Please contact your administrator." ?: "This terminal has been disabled. Please contact your administrator."
_uiState.update { _uiState.update {
it.copy( it.copy(
isLoading = false, isLoading = false,
@ -171,11 +157,6 @@ class TmsSetupViewModel @Inject constructor(
errorMessage = msg errorMessage = msg
) )
} }
// FIX 2: Actually trigger the navigation event so the UI leaves this screen
viewModelScope.launch {
_navigateToDisable.emit(msg)
}
return return
} }
@ -207,10 +188,6 @@ class TmsSetupViewModel @Inject constructor(
viewModelScope.launch { _navigateToDashboard.emit(Unit) } viewModelScope.launch { _navigateToDashboard.emit(Unit) }
} }
fun clearTerminalDisabledState() {
_uiState.update { it.copy(isTerminalDisabled = false) }
}
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
private fun buildRequest(): SiriusRequest { private fun buildRequest(): SiriusRequest {
return try { return try {

View File

@ -1,7 +1,6 @@
package com.mob.utsmyanmar.ui.transaction_result package com.mob.utsmyanmar.ui.transaction_result
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.Observer
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.mob.utsmyanmar.model.ecr.ECRResultStatus import com.mob.utsmyanmar.model.ecr.ECRResultStatus
import com.mob.utsmyanmar.utils.CoreUtils import com.mob.utsmyanmar.utils.CoreUtils
@ -48,8 +47,6 @@ class TransactionResultViewModel @Inject constructor(
private var started = false private var started = false
private var resultTimeoutJob: Job? = null private var resultTimeoutJob: Job? = null
private var printStatusObserver: Observer<PrintStatus?>? = null
private var observedSharedViewModel: SharedViewModel? = null
fun onEvent( fun onEvent(
event: TransactionResultEvent, event: TransactionResultEvent,
@ -199,11 +196,7 @@ class TransactionResultViewModel @Inject constructor(
} }
private fun observePrintStatus(sharedViewModel: SharedViewModel) { private fun observePrintStatus(sharedViewModel: SharedViewModel) {
printStatusObserver?.let { observer -> sharedViewModel.printXStatus.observeForever { status ->
observedSharedViewModel?.printXStatus?.removeObserver(observer)
}
printStatusObserver = Observer<PrintStatus?> { status ->
status ?: return@Observer
when (status) { when (status) {
PrintStatus.EMPTY_PAPER_ROLL -> { PrintStatus.EMPTY_PAPER_ROLL -> {
startPrintProcess(sharedViewModel, false) startPrintProcess(sharedViewModel, false)
@ -216,18 +209,6 @@ class TransactionResultViewModel @Inject constructor(
else -> Unit else -> Unit
} }
} }
observedSharedViewModel = sharedViewModel
sharedViewModel.printXStatus.observeForever(printStatusObserver!!)
}
override fun onCleared() {
resultTimeoutJob?.cancel()
printStatusObserver?.let { observer ->
observedSharedViewModel?.printXStatus?.removeObserver(observer)
}
printStatusObserver = null
observedSharedViewModel = null
super.onCleared()
} }
private fun startPrintProcess( private fun startPrintProcess(
@ -239,16 +220,7 @@ class TransactionResultViewModel @Inject constructor(
override fun paperRollIsReady() { override fun paperRollIsReady() {
if (isSettlement) { if (isSettlement) {
sharedViewModel.startPrintProcessSettlement( sharedViewModel.startPrintProcessSettlement()
onSuccess = ::navigateMain,
onFailure = {
sendUiEvent(
TransactionResultUiEvent.ShowPrinterDialog(
"Settlement report printing failed"
)
)
}
)
} else { } else {
sharedViewModel.startPrintProcess() sharedViewModel.startPrintProcess()
} }

View File

@ -43,7 +43,7 @@ object MockData {
cardNo="6244310039574344", cardNo="6244310039574344",
expDate="3011", expDate="3011",
cardScheme="MPU", cardScheme="MPU",
cardHolderName="Nay Nwe Ei Kyaw Phyo", cardHolderName="Htin Kyaw Win",
iccData="6244310039574344=30112011558125900000" iccData="6244310039574344=30112011558125900000"
) )

View File

@ -78,8 +78,6 @@ class SharedViewModel @Inject constructor(
val isEmv = SingleLiveEvent<Boolean>() val isEmv = SingleLiveEvent<Boolean>()
private val settlementStatus = MutableLiveData<Boolean>() private val settlementStatus = MutableLiveData<Boolean>()
private var settlementPrintInProgress = false
private var settlementPrintCompletedKey: String? = null
private val wavePayStatus = MutableLiveData<Boolean>() private val wavePayStatus = MutableLiveData<Boolean>()
@ -421,46 +419,17 @@ class SharedViewModel @Inject constructor(
} }
} }
fun startPrintProcessSettlement( fun startPrintProcessSettlement() {
onSuccess: () -> Unit,
onFailure: () -> Unit
) {
val detail = payDetail.value ?: run { val detail = payDetail.value ?: return
onFailure()
return
}
val printKey = "${detail.batchNo}:${detail.voucherNo}"
synchronized(this) {
if (settlementPrintCompletedKey == printKey || settlementPrintInProgress) {
return
}
settlementPrintInProgress = true
}
PrintXReceipt.getInstance() PrintXReceipt.getInstance()
.printSmileSettlementReport(detail, object : PrintXStatus { .printSmileSettlementReport(detail, object : PrintXStatus {
private var callbackHandled = false
override fun onSuccess() { override fun onSuccess() {
synchronized(this) {
if (callbackHandled) return
callbackHandled = true
settlementPrintInProgress = false
settlementPrintCompletedKey = printKey
}
onSuccess()
} }
override fun onFailure() { override fun onFailure() {
synchronized(this) {
if (callbackHandled) return
callbackHandled = true
settlementPrintInProgress = false
}
onFailure()
} }
}) })
} }

View File

@ -37,9 +37,6 @@ public interface PayDetailDao {
@Delete @Delete
void deletePayDetail(PayDetail payDetail); void deletePayDetail(PayDetail payDetail);
@Query("DELETE FROM paydetail WHERE transactionType = 10")
void deleteReversalTransactions();
// 13 = CASH DEPOSIT & 9 = CASH ADVANCE & 14 = FUND TRANSFER // 13 = CASH DEPOSIT & 9 = CASH ADVANCE & 14 = FUND TRANSFER
@Query("SELECT * FROM paydetail WHERE isSettle = 0 AND transactionType = 13 OR transactionType = 9 AND isSettle = 0 OR transactionType = 14 AND isSettle = 0") @Query("SELECT * FROM paydetail WHERE isSettle = 0 AND transactionType = 13 OR transactionType = 9 AND isSettle = 0 OR transactionType = 14 AND isSettle = 0")
LiveData<List<PayDetail>> getSettlement(); LiveData<List<PayDetail>> getSettlement();

View File

@ -209,9 +209,5 @@ public class Repository {
payDetailDao.deletePayDetail(payDetail); payDetailDao.deletePayDetail(payDetail);
} }
public void deleteReversalTransactions() {
payDetailDao.deleteReversalTransactions();
}
public LiveData<List<PayDetail>> getAllTrans() { return payDetailDao.getAllTrans(); } public LiveData<List<PayDetail>> getAllTrans() { return payDetailDao.getAllTrans(); }
} }

View File

@ -13,6 +13,8 @@ import java.text.DecimalFormat;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.Locale; import java.util.Locale;
import sunmi.sunmiui.utils.LogUtil;
public class PrintUtils { public class PrintUtils {
public static PrintUtils getInstance() { public static PrintUtils getInstance() {
@ -263,18 +265,18 @@ public class PrintUtils {
public String processNameSpaceSecondLine(String input) { public String processNameSpaceSecondLine(String input) {
String[] words = input.split(" "); String[] words = input.split(" ");
if (input.length() < 20) { if (input.length() < 15) {
return ""; return "";
} }
if (words.length == 3 && input.length() > 20) { if (words.length == 3 && input.length() > 15) {
return words[2]; return words[2];
} else if (words.length == 2 && input.length() <= 20) { } else if (words.length == 2 && input.length() <= 15) {
return ""; return "";
} else if (words.length == 2 && input.length() > 20) { } else if (words.length == 2 && input.length() > 15) {
return words[1]; return words[1];
} else { } else {
return input.substring(20); return input.substring(15);
} }
} }
} }