From 6800fc6c416f0b1a877689c9fc3ff89a9fcda465 Mon Sep 17 00:00:00 2001 From: soraefir Date: Wed, 30 Sep 2026 22:51:10 +0200 Subject: [PATCH] Direct Settlement --- .../project/settle/ProjectSettlementScreen.kt | 12 +++++- .../net/helcel/cowspent/util/SupportUtil.kt | 39 +++++++++++++++++-- app/src/main/res/values-de-rDE/strings.xml | 1 + app/src/main/res/values-es-rES/strings.xml | 1 + app/src/main/res/values-fr-rFR/strings.xml | 1 + app/src/main/res/values-ru-rRU/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 7 files changed, 51 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/net/helcel/cowspent/android/project/settle/ProjectSettlementScreen.kt b/app/src/main/java/net/helcel/cowspent/android/project/settle/ProjectSettlementScreen.kt index 364df84..e1b06ea 100644 --- a/app/src/main/java/net/helcel/cowspent/android/project/settle/ProjectSettlementScreen.kt +++ b/app/src/main/java/net/helcel/cowspent/android/project/settle/ProjectSettlementScreen.kt @@ -48,8 +48,10 @@ import net.helcel.cowspent.model.Transaction import net.helcel.cowspent.model.UserItem import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper import net.helcel.cowspent.util.SupportUtil +import net.helcel.cowspent.util.SupportUtil.SETTLE_DIRECT import net.helcel.cowspent.util.SupportUtil.SETTLE_OPTIMAL import net.helcel.cowspent.util.SupportUtil.settleBills +import net.helcel.cowspent.util.SupportUtil.settleBillsDirect @Composable fun ProjectSettlementDialogContent( @@ -60,10 +62,12 @@ fun ProjectSettlementDialogContent( onDismiss: () -> Unit ) { val centerNoneStr = stringResource(R.string.center_none) + val centerDirectStr = stringResource(R.string.center_none_direct) val memberList = remember(proj.id) { db.getMembersOfProject(proj.id, null) } - val userList = remember(memberList, centerNoneStr) { + val userList = remember(memberList, centerNoneStr, centerDirectStr) { buildList { add(UserItem(SETTLE_OPTIMAL, centerNoneStr)) + add(UserItem(SETTLE_DIRECT, centerDirectStr)) addAll(memberList.map { UserItem(it.id, it.name) }) } } @@ -91,7 +95,11 @@ fun ProjectSettlementDialogContent( } val transactions = remember(selectedMemberId, membersBalance, membersSortedByName) { - settleBills(membersSortedByName, membersBalance, selectedMemberId) + if (selectedMemberId == SETTLE_DIRECT) { + settleBillsDirect(membersSortedByName, db.getBillsOfProject(proj.id)) + } else { + settleBills(membersSortedByName, membersBalance, selectedMemberId) + } } ProjectSettlementUI( diff --git a/app/src/main/java/net/helcel/cowspent/util/SupportUtil.kt b/app/src/main/java/net/helcel/cowspent/util/SupportUtil.kt index c2b0cf9..7e80821 100644 --- a/app/src/main/java/net/helcel/cowspent/util/SupportUtil.kt +++ b/app/src/main/java/net/helcel/cowspent/util/SupportUtil.kt @@ -141,7 +141,7 @@ object SupportUtil { membersBalance[b.payerId] = balPayer + amount val paid = membersPaid[b.payerId] ?: 0.0 membersPaid[b.payerId] = paid + amount - + var nbOwerShares = 0.0 for (bo in b.billOwers) { nbOwerShares += membersWeight[bo.memberId] ?: 0.0 @@ -167,6 +167,39 @@ object SupportUtil { } const val SETTLE_OPTIMAL: Long = 0 + const val SETTLE_DIRECT: Long = -1 + + @JvmStatic + fun settleBillsDirect(members: List, bills: List): List { + val membersWeight = members.associate { it.id to it.weight } + // owed[debtor][creditor] = total the debtor owes the creditor over all bills + val owed = HashMap>() + + for (b in bills) { + if (b.state == DBBill.STATE_DELETED) continue + val nbOwerShares = b.billOwers.sumOf { membersWeight[it.memberId] ?: 0.0 } + if (nbOwerShares <= 0.0) continue + for (bo in b.billOwers) { + if (bo.memberId == b.payerId) continue + val share = b.amount / nbOwerShares * (membersWeight[bo.memberId] ?: 0.0) + val row = owed.getOrPut(bo.memberId) { HashMap() } + row[b.payerId] = (row[b.payerId] ?: 0.0) + share + } + } + + val results: MutableList = ArrayList() + for (debtor in members) { + for (creditor in members) { + if (debtor.id == creditor.id) continue + val net = (owed[debtor.id]?.get(creditor.id) ?: 0.0) - + (owed[creditor.id]?.get(debtor.id) ?: 0.0) + if (round2(net) > 0.0) { + results.add(Transaction(debtor.id, creditor.id, round2(net))) + } + } + } + return results + } @JvmStatic fun settleBills( @@ -233,11 +266,11 @@ object SupportUtil { if (cd1.balance < cd2.balance) 1 else -1 } } - + for (c in crediters) { Log.e(SupportUtil::class.java.simpleName, "* " + c.memberId + " : " + c.balance) } - + debiters.sortWith { cd2, cd1 -> if (cd1.balance == cd2.balance) { 0 diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 0a38959..cb4b76a 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -209,6 +209,7 @@ Keine (optimal) + Keine (direkt) Wer zahlt An wen Betrag diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 2876e61..d53f914 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -210,6 +210,7 @@ Ninguno (óptimo) + Ninguno (directo) Quién paga A quién Importe diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index 70ebfde..1d6c59e 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -213,6 +213,7 @@ Aucun (optimal) + Aucun (direct) Qui paie À qui Montant diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 48fcf24..48f7908 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -209,6 +209,7 @@ Нет (оптимально) + Нет (напрямую) Кто платит Кому Сумма diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 44e3d01..bc8f2a8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -247,6 +247,7 @@ None (Optimal) + None (Direct) Who pays To whom Amount