display outstanding and unbilled credit card values and also fix 00.00 values on credit card statement
Auto Tag on Version Change / check-version (push) Failing after 12m48s

This commit is contained in:
2026-06-13 22:55:54 +05:00
parent ae18a8c6c8
commit 52d2eb235b
6 changed files with 93 additions and 69 deletions
+2 -2
View File
@@ -4,10 +4,10 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-06-03T08:28:30.389803148Z">
<DropdownSelection timestamp="2026-06-13T17:53:06.478193524Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="Default" identifier="serial=10.0.1.245:5555;connection=d182cf37" />
<DeviceId pluginId="PhysicalDevice" identifier="serial=a703e092" />
</handle>
</Target>
</DropdownSelection>
@@ -9,6 +9,12 @@ import sh.sar.basedbank.api.models.BankTransaction
import java.text.SimpleDateFormat
import java.util.Locale
data class BmlCardHistoryResult(
val statement: List<BankTransaction>,
val outstanding: List<BankTransaction>,
val unbilled: List<BankTransaction>
)
class BmlHistoryClient {
private val client = newBmlApiClient()
@@ -70,7 +76,7 @@ class BmlHistoryClient {
accountDisplayName: String,
accountNumber: String,
month: String
): List<BankTransaction> {
): BmlCardHistoryResult {
val body = """{"card":"$cardId","month":"$month"}"""
.toRequestBody("application/json".toMediaType())
val resp = client.newCall(
@@ -81,76 +87,56 @@ class BmlHistoryClient {
.build()
).execute()
val code = resp.code
val json = resp.body?.string() ?: return emptyList()
val json = resp.body?.string() ?: return BmlCardHistoryResult(emptyList(), emptyList(), emptyList())
resp.close()
if (code == 401 || code == 419) throw AuthExpiredException()
if (code in 500..599) throw BankServerException("BML")
return try {
val root = JSONObject(json)
if (!root.optBoolean("success")) return emptyList()
val payload = root.optJSONObject("payload") ?: return emptyList()
val result = mutableListOf<BankTransaction>()
if (!root.optBoolean("success")) return BmlCardHistoryResult(emptyList(), emptyList(), emptyList())
val payload = root.optJSONObject("payload")
?: return BmlCardHistoryResult(emptyList(), emptyList(), emptyList())
val authDetails = payload.optJSONObject("outstanding")
?.optJSONArray("CardOutStdAuthDetails")
if (authDetails != null) {
for (i in 0 until authDetails.length()) {
val item = authDetails.getJSONObject(i)
result.add(BankTransaction(
id = "auth_${item.optString("TranApprCode")}_$i",
date = item.optString("DateTime"),
description = item.optString("TranDesc").trim(),
amount = item.optDouble("BillingAmount", 0.0),
currency = item.optString("BillingCcy", "MVR"),
counterpartyName = null,
reference = item.optString("TranApprCode").takeIf { it.isNotBlank() },
accountNumber = accountNumber,
accountDisplayName = accountDisplayName,
source = "BML_CARD"
))
}
}
val outstanding = parseCardArray(
payload.optJSONObject("outstanding")?.optJSONArray("CardOutStdAuthDetails"),
idPrefix = "auth", accountNumber, accountDisplayName
)
val unbilled = parseCardArray(
payload.optJSONObject("unbilled")?.optJSONArray("CardUnbillTxnDetails"),
idPrefix = "unbilled", accountNumber, accountDisplayName
)
val statement = parseCardArray(
payload.optJSONArray("cardstatement"),
idPrefix = "stmt", accountNumber, accountDisplayName
)
val unbilled = payload.optJSONObject("unbilled")
?.optJSONArray("CardUnbillTxnDetails")
if (unbilled != null) {
for (i in 0 until unbilled.length()) {
val item = unbilled.getJSONObject(i)
result.add(BankTransaction(
id = "unbilled_${item.optString("TranApprCode")}_$i",
date = item.optString("DateTime"),
description = item.optString("TranDesc").trim(),
amount = item.optDouble("BillingAmount", 0.0),
currency = item.optString("BillingCcy", "MVR"),
counterpartyName = null,
reference = item.optString("TranApprCode").takeIf { it.isNotBlank() },
accountNumber = accountNumber,
accountDisplayName = accountDisplayName,
source = "BML_CARD"
))
}
}
BmlCardHistoryResult(statement, outstanding, unbilled)
} catch (_: Exception) { BmlCardHistoryResult(emptyList(), emptyList(), emptyList()) }
}
val statement = payload.optJSONArray("cardstatement")
if (statement != null) {
for (i in 0 until statement.length()) {
val item = statement.getJSONObject(i)
result.add(BankTransaction(
id = "stmt_${item.optString("TranRef", i.toString())}",
date = item.optString("TransDate", item.optString("TranDate", "")),
description = item.optString("TranDesc", item.optString("Description", "")).trim(),
amount = -item.optDouble("TranAmount", 0.0),
currency = item.optString("TranCcy", "MVR"),
counterpartyName = null,
reference = item.optString("TranRef").takeIf { it.isNotBlank() },
accountNumber = accountNumber,
accountDisplayName = accountDisplayName,
source = "BML_CARD"
))
}
}
result
} catch (_: Exception) { emptyList() }
private fun parseCardArray(
arr: org.json.JSONArray?,
idPrefix: String,
accountNumber: String,
accountDisplayName: String
): List<BankTransaction> {
if (arr == null) return emptyList()
return (0 until arr.length()).map { i ->
val item = arr.getJSONObject(i)
val ref = item.optString("TranApprCode")
BankTransaction(
id = "${idPrefix}_${ref.ifBlank { i.toString() }}",
date = item.optString("DateTime"),
description = item.optString("TranDesc").trim(),
amount = item.optDouble("BillingAmount", 0.0),
currency = item.optString("BillingCcy", "MVR"),
counterpartyName = null,
reference = ref.takeIf { it.isNotBlank() },
accountNumber = accountNumber,
accountDisplayName = accountDisplayName,
source = "BML_CARD"
)
}
}
fun fetchPendingHistory(
@@ -74,9 +74,18 @@ class AccountHistoryAdapter(
}
fun setPendingTransactions(transactions: List<BankTransaction>) {
setLeadingSections(listOf("Pending" to transactions))
}
/**
* Sets one or more labeled sections that render above the main statement list
* (e.g. card "Outstanding" + "Unbilled"). Empty sections are skipped.
*/
fun setLeadingSections(sections: List<Pair<String, List<BankTransaction>>>) {
pendingItems.clear()
if (transactions.isNotEmpty()) {
pendingItems.add(Item.DateHeader("Pending"))
for ((label, transactions) in sections) {
if (transactions.isEmpty()) continue
pendingItems.add(Item.DateHeader(label))
for (trx in transactions) pendingItems.add(Item.Trx(trx, showDate = true))
}
notifyDataSetChanged()
@@ -228,6 +228,13 @@ class AccountHistoryFragment : Fragment() {
}
(activity as? HomeActivity)?.hideConnectivityBanner()
fetcher.takeCardPendingSections()?.let { (outstanding, unbilled) ->
adapter.setLeadingSections(listOf(
"Outstanding" to outstanding,
"Unbilled" to unbilled
))
}
if (transactions.isNotEmpty()) {
val existingIds = allTransactions.map { it.id }.toHashSet()
val newOnes = transactions.filter { it.id !in existingIds }
@@ -209,13 +209,14 @@ class TransferHistoryFragment : Fragment() {
cal.add(Calendar.MONTH, -state.cardMonthOffset)
val month = SimpleDateFormat("yyyyMM", Locale.US).format(cal.time)
state.cardMonthOffset++
BmlHistoryClient().fetchCardHistory(
val cardResult = BmlHistoryClient().fetchCardHistory(
session = session,
cardId = state.account.internalId,
accountDisplayName = state.account.accountBriefName,
accountNumber = state.account.accountNumber,
month = month
)
cardResult.statement + cardResult.outstanding + cardResult.unbilled
}
else -> {
val session = app.bmlSessionFor(state.account) ?: return@async emptyList()
@@ -35,6 +35,21 @@ class HistoryFetcher(private val account: BankAccount) {
// BML card pagination (month-based)
private var cardMonthOffset = 0
private var pendingCardOutstanding: List<BankTransaction>? = null
private var pendingCardUnbilled: List<BankTransaction>? = null
/**
* Returns and clears the card outstanding + unbilled lists captured on the first card
* fetch. Each list is only ever returned once.
*/
fun takeCardPendingSections(): Pair<List<BankTransaction>, List<BankTransaction>>? {
val o = pendingCardOutstanding
val u = pendingCardUnbilled
if (o == null && u == null) return null
pendingCardOutstanding = null
pendingCardUnbilled = null
return Pair(o ?: emptyList(), u ?: emptyList())
}
// Fahipay pagination
private var fahipayNextStart = 0
@@ -90,16 +105,22 @@ class HistoryFetcher(private val account: BankAccount) {
private fun fetchBmlCard(app: BasedBankApp): List<BankTransaction> {
val session = app.bmlSessionFor(account) ?: return emptyList()
val cal = Calendar.getInstance()
val isFirstFetch = cardMonthOffset == 0
cal.add(Calendar.MONTH, -cardMonthOffset)
val month = SimpleDateFormat("yyyyMM", Locale.US).format(cal.time)
cardMonthOffset++
return BmlHistoryClient().fetchCardHistory(
val result = BmlHistoryClient().fetchCardHistory(
session = session,
cardId = account.internalId,
accountDisplayName = account.accountBriefName,
accountNumber = account.accountNumber,
month = month
)
if (isFirstFetch) {
pendingCardOutstanding = result.outstanding
pendingCardUnbilled = result.unbilled
}
return result.statement
}
private fun fetchBmlCasa(app: BasedBankApp): List<BankTransaction> {