eetlijst-python 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. eetlijst/__init__.py +3 -0
  2. eetlijst/eetlijst.py +37 -0
  3. eetlijst/exceptions.py +2 -0
  4. eetlijst/generated/__init__.py +1153 -0
  5. eetlijst/generated/all_attendances.py +13 -0
  6. eetlijst/generated/all_events.py +13 -0
  7. eetlijst/generated/all_expenses.py +13 -0
  8. eetlijst/generated/all_groups.py +27 -0
  9. eetlijst/generated/all_settlements.py +13 -0
  10. eetlijst/generated/all_users_in_group.py +20 -0
  11. eetlijst/generated/app_status.py +16 -0
  12. eetlijst/generated/async_base_client.py +393 -0
  13. eetlijst/generated/automatic_events.py +18 -0
  14. eetlijst/generated/base_model.py +28 -0
  15. eetlijst/generated/client.py +1748 -0
  16. eetlijst/generated/create_expense.py +15 -0
  17. eetlijst/generated/create_group.py +15 -0
  18. eetlijst/generated/create_list_item.py +15 -0
  19. eetlijst/generated/create_many_list_items.py +20 -0
  20. eetlijst/generated/create_settlement.py +15 -0
  21. eetlijst/generated/enums.py +555 -0
  22. eetlijst/generated/exceptions.py +83 -0
  23. eetlijst/generated/fragments.py +237 -0
  24. eetlijst/generated/get_attendance.py +17 -0
  25. eetlijst/generated/get_event.py +15 -0
  26. eetlijst/generated/get_group.py +20 -0
  27. eetlijst/generated/get_user.py +13 -0
  28. eetlijst/generated/get_user_in_group.py +20 -0
  29. eetlijst/generated/group_total_expense.py +45 -0
  30. eetlijst/generated/input_types.py +4702 -0
  31. eetlijst/generated/join_group.py +17 -0
  32. eetlijst/generated/list_items.py +13 -0
  33. eetlijst/generated/remove_account.py +16 -0
  34. eetlijst/generated/settle_unsettled_expenses.py +21 -0
  35. eetlijst/generated/settlement_expenses.py +13 -0
  36. eetlijst/generated/update_attendance.py +17 -0
  37. eetlijst/generated/update_event.py +15 -0
  38. eetlijst/generated/update_expense.py +20 -0
  39. eetlijst/generated/update_expense_distribution.py +23 -0
  40. eetlijst/generated/update_group.py +15 -0
  41. eetlijst/generated/update_list_item.py +15 -0
  42. eetlijst/generated/update_many_attendance.py +22 -0
  43. eetlijst/generated/update_user.py +15 -0
  44. eetlijst/generated/update_user_in_group.py +17 -0
  45. eetlijst/generated/update_users_in_group.py +25 -0
  46. eetlijst/services/__init__.py +9 -0
  47. eetlijst/services/app/__init__.py +1 -0
  48. eetlijst/services/app/app.py +20 -0
  49. eetlijst/services/app/transformers.py +10 -0
  50. eetlijst/services/event_attendance/__init__.py +1 -0
  51. eetlijst/services/event_attendance/event_attendance.py +94 -0
  52. eetlijst/services/event_attendance/transformers.py +58 -0
  53. eetlijst/services/events/__init__.py +1 -0
  54. eetlijst/services/events/events.py +148 -0
  55. eetlijst/services/events/transformers.py +67 -0
  56. eetlijst/services/expenses/__init__.py +1 -0
  57. eetlijst/services/expenses/expenses.py +98 -0
  58. eetlijst/services/expenses/transformers.py +73 -0
  59. eetlijst/services/expenses/utils.py +31 -0
  60. eetlijst/services/group_list/__init__.py +1 -0
  61. eetlijst/services/group_list/group_list.py +95 -0
  62. eetlijst/services/group_list/transformers.py +38 -0
  63. eetlijst/services/group_users/__init__.py +1 -0
  64. eetlijst/services/group_users/group_users.py +130 -0
  65. eetlijst/services/group_users/transformers.py +84 -0
  66. eetlijst/services/groups/__init__.py +1 -0
  67. eetlijst/services/groups/groups.py +84 -0
  68. eetlijst/services/groups/transformers.py +140 -0
  69. eetlijst/services/settlements/__init__.py +1 -0
  70. eetlijst/services/settlements/settlements.py +225 -0
  71. eetlijst/services/settlements/transformers.py +95 -0
  72. eetlijst/services/settlements/utils.py +84 -0
  73. eetlijst/services/users/__init__.py +1 -0
  74. eetlijst/services/users/transformers.py +103 -0
  75. eetlijst/services/users/users.py +44 -0
  76. eetlijst/utils/__init__.py +0 -0
  77. eetlijst/utils/datetime.py +25 -0
  78. eetlijst_python-0.0.1.dist-info/METADATA +80 -0
  79. eetlijst_python-0.0.1.dist-info/RECORD +81 -0
  80. eetlijst_python-0.0.1.dist-info/WHEEL +5 -0
  81. eetlijst_python-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,84 @@
1
+ from dataclasses import dataclass
2
+ from typing import TYPE_CHECKING, List, Optional
3
+
4
+ from eetlijst.generated import GraphQlClient, order_by
5
+ from eetlijst.generated.input_types import (
6
+ String_comparison_exp,
7
+ eetschema_group_order_by,
8
+ eetschema_group_set_input,
9
+ eetschema_users_in_group_bool_exp,
10
+ eetschema_users_in_group_order_by,
11
+ )
12
+
13
+ from eetlijst.services.group_list import GroupList
14
+ from eetlijst.services.groups.transformers import (
15
+ GroupResult,
16
+ transform_all_groups,
17
+ transform_create_group,
18
+ transform_get_group,
19
+ transform_update_group,
20
+ )
21
+
22
+ if TYPE_CHECKING:
23
+ from eetlijst.services.group_users import GroupUsers
24
+
25
+
26
+ @dataclass
27
+ class Groups:
28
+ _client: GraphQlClient
29
+ users: GroupUsers
30
+ list: GroupList
31
+
32
+ async def get(
33
+ self,
34
+ group_id: str,
35
+ include_users: bool = False,
36
+ ) -> GroupResult:
37
+ result = await self._client.get_group(group_id=group_id)
38
+ return transform_get_group(result, include_users=include_users)
39
+
40
+ async def all(
41
+ self,
42
+ user_id: str,
43
+ include_users: bool = False,
44
+ include_inactive_users: bool = False,
45
+ where: Optional[eetschema_users_in_group_bool_exp] = None,
46
+ order: Optional[List[eetschema_users_in_group_order_by]] = None,
47
+ limit: Optional[int] = None,
48
+ ) -> List[GroupResult]:
49
+ where_data = (
50
+ where.model_copy(update={"user_id": String_comparison_exp(_eq=user_id)})
51
+ if where is not None
52
+ else eetschema_users_in_group_bool_exp(
53
+ user_id=String_comparison_exp(_eq=user_id)
54
+ )
55
+ )
56
+
57
+ order_data = order or [
58
+ eetschema_users_in_group_order_by(
59
+ group=eetschema_group_order_by(created_at=order_by.desc)
60
+ )
61
+ ]
62
+
63
+ result = await self._client.all_groups(
64
+ where=where_data,
65
+ order=order_data,
66
+ limit=limit,
67
+ include_users=include_users,
68
+ include_inactive_users=include_inactive_users,
69
+ )
70
+
71
+ return transform_all_groups(result, include_users=include_users)
72
+
73
+ async def create(self, name: str, user_id: str) -> GroupResult:
74
+ result = await self._client.create_group(name=name, user_id=user_id)
75
+ return transform_create_group(result)
76
+
77
+ async def update(
78
+ self, group_id: str, data: eetschema_group_set_input
79
+ ) -> GroupResult:
80
+ result = await self._client.update_group(
81
+ group_id=group_id,
82
+ set_=data,
83
+ )
84
+ return transform_update_group(result)
@@ -0,0 +1,140 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from eetlijst.generated.all_groups import AllGroups
7
+ from eetlijst.generated.create_group import CreateGroup
8
+ from eetlijst.generated.fragments import GroupFields, UserInGroupFields
9
+ from eetlijst.generated.get_group import GetGroup
10
+ from eetlijst.generated.update_group import UpdateGroup
11
+
12
+ from eetlijst.exceptions import EetlijstException
13
+
14
+ from eetlijst.services.users.transformers import UserResult, transform_user
15
+
16
+
17
+ class GroupSummaryResult(BaseModel):
18
+ payed_total: float
19
+ user_id: str
20
+
21
+
22
+ class UserInGroupResult(BaseModel):
23
+ order: Optional[int] = None
24
+ start_holliday: Optional[datetime] = None
25
+ end_holliday: Optional[datetime] = None
26
+ monday: Optional[bool] = None
27
+ tuesday: Optional[bool] = None
28
+ wednesday: Optional[bool] = None
29
+ thursday: Optional[bool] = None
30
+ friday: Optional[bool] = None
31
+ saturday: Optional[bool] = None
32
+ sunday: Optional[bool] = None
33
+ user: Optional[UserResult] = None
34
+
35
+
36
+ class GroupResult(BaseModel):
37
+ id: str
38
+ name: str
39
+ default_close_time: Optional[datetime]
40
+ created_at: datetime
41
+ created_at_eetlijst: Optional[datetime]
42
+ statistics_start_date: Optional[datetime]
43
+ statistics_end_date: Optional[datetime]
44
+ invite_uuid: str
45
+ invite_open: bool
46
+ description: Optional[str]
47
+ summary: list[GroupSummaryResult]
48
+ users: list[UserInGroupResult] = []
49
+
50
+
51
+ def transform_group(
52
+ group: Optional[GroupFields],
53
+ users: Optional[list[UserInGroupResult]] = None,
54
+ ) -> GroupResult:
55
+ if not group:
56
+ raise EetlijstException("Group not found")
57
+
58
+ filtered_summary = [
59
+ GroupSummaryResult(
60
+ payed_total=float(entry.payed_total),
61
+ user_id=entry.user_id,
62
+ )
63
+ for entry in (group.summary or [])
64
+ if entry and entry.payed_total is not None and entry.user_id is not None
65
+ ]
66
+
67
+ group_data = group.model_dump(exclude={"summary"})
68
+ return GroupResult(
69
+ **group_data,
70
+ summary=filtered_summary,
71
+ users=users or [],
72
+ )
73
+
74
+
75
+ def transform_user_in_group(
76
+ user_in_group: Optional[UserInGroupFields],
77
+ ) -> UserInGroupResult:
78
+ if not user_in_group:
79
+ raise EetlijstException("User in group not found")
80
+
81
+ user_data = transform_user(user_in_group.user) if user_in_group.user else None
82
+ data = user_in_group.model_dump(exclude={"user"})
83
+
84
+ return UserInGroupResult(
85
+ **data,
86
+ user=user_data,
87
+ )
88
+
89
+
90
+ def transform_create_group(result: CreateGroup) -> GroupResult:
91
+ if not result.group:
92
+ raise EetlijstException("Failed to create group")
93
+
94
+ return transform_group(result.group)
95
+
96
+
97
+ def transform_update_group(result: UpdateGroup) -> GroupResult:
98
+ if not result.group:
99
+ raise EetlijstException("Failed to update group")
100
+
101
+ return transform_group(result.group)
102
+
103
+
104
+ def transform_all_groups(
105
+ result: AllGroups,
106
+ include_users: bool = False,
107
+ ) -> list[GroupResult]:
108
+ transformed_groups: list[GroupResult] = []
109
+ for user_in_group in result.eetschema_users_in_group:
110
+ if not user_in_group.group:
111
+ continue
112
+
113
+ users = (
114
+ [transform_user_in_group(u) for u in user_in_group.group.users_in_groups]
115
+ if include_users and user_in_group.group.users_in_groups
116
+ else []
117
+ )
118
+
119
+ transformed_groups.append(transform_group(user_in_group.group, users=users))
120
+
121
+ return transformed_groups
122
+
123
+
124
+ def transform_get_group(
125
+ result: GetGroup,
126
+ include_users: bool = False,
127
+ ) -> GroupResult:
128
+ if not result.eetschema_group_by_pk:
129
+ raise EetlijstException("Failed to get group")
130
+
131
+ users = (
132
+ [
133
+ transform_user_in_group(u)
134
+ for u in result.eetschema_group_by_pk.users_in_groups
135
+ ]
136
+ if include_users and result.eetschema_group_by_pk.users_in_groups
137
+ else []
138
+ )
139
+
140
+ return transform_group(result.eetschema_group_by_pk, users=users)
@@ -0,0 +1 @@
1
+ from .settlements import Settlements
@@ -0,0 +1,225 @@
1
+ import asyncio
2
+ from dataclasses import dataclass
3
+ from datetime import datetime, timezone
4
+ from typing import List, Optional, TypedDict
5
+
6
+ from eetlijst.generated import GraphQlClient, order_by
7
+ from eetlijst.generated.fragments import ExpenseFields
8
+ from eetlijst.generated.input_types import (
9
+ Boolean_comparison_exp,
10
+ eetschema_expense_bool_exp,
11
+ eetschema_expense_distribution_insert_input,
12
+ eetschema_expense_order_by,
13
+ eetschema_expense_set_input,
14
+ eetschema_settlements_bool_exp,
15
+ eetschema_settlements_order_by,
16
+ uuid_comparison_exp,
17
+ )
18
+
19
+ from eetlijst.services.expenses.transformers import (
20
+ transform_create_expense,
21
+ transform_update_expense,
22
+ )
23
+ from eetlijst.services.expenses.utils import calculate_balances_from_expenses
24
+ from eetlijst.services.settlements.transformers import (
25
+ SettlementResult,
26
+ transform_create_settlement,
27
+ transform_settle_unsettled_expenses,
28
+ transform_settlement,
29
+ transform_settlement_expenses,
30
+ )
31
+ from eetlijst.services.settlements.utils import (
32
+ CalculatedAdjustmentExpense,
33
+ calculate_adjustment_expenses,
34
+ )
35
+
36
+
37
+ class SettleResult(TypedDict):
38
+ id: str
39
+ expenses: list[ExpenseFields]
40
+ adjustments: list[ExpenseFields]
41
+
42
+
43
+ @dataclass
44
+ class Settlements:
45
+ _client: GraphQlClient
46
+
47
+ async def get(self, settlement_id: str) -> Optional[SettlementResult]:
48
+ result = await self._client.all_settlements(
49
+ where=eetschema_settlements_bool_exp(
50
+ id=uuid_comparison_exp(_eq=settlement_id)
51
+ )
52
+ )
53
+
54
+ return transform_settlement(result.eetschema_settlements[0])
55
+
56
+ async def all(
57
+ self,
58
+ group_id: str,
59
+ where: Optional[eetschema_settlements_bool_exp] = None,
60
+ order: Optional[list[eetschema_settlements_order_by]] = None,
61
+ limit: Optional[int] = None,
62
+ ) -> list[SettlementResult]:
63
+ group_filter = uuid_comparison_exp(_eq=group_id)
64
+ where_data = (
65
+ where.model_copy(update={"group_id": group_filter})
66
+ if where is not None
67
+ else eetschema_settlements_bool_exp(group_id=group_filter)
68
+ )
69
+
70
+ order_data = order or [eetschema_settlements_order_by(created_at=order_by.desc)]
71
+
72
+ result = await self._client.all_settlements(
73
+ where=where_data,
74
+ order=order_data,
75
+ limit=limit,
76
+ )
77
+ return [transform_settlement(s) for s in result.eetschema_settlements]
78
+
79
+ async def create(self, group_id: str) -> SettlementResult:
80
+ result = await self._client.create_settlement(group_id=group_id)
81
+ return transform_create_settlement(result)
82
+
83
+ async def settle(
84
+ self,
85
+ group_id: str,
86
+ settlement_id: Optional[str] = None,
87
+ do_not_create_adjustment_expenses: bool = False,
88
+ select: Optional[eetschema_expense_bool_exp] = None,
89
+ ) -> SettleResult:
90
+ if settlement_id is None:
91
+ settlement = await self.create(group_id=group_id)
92
+ settlement_id = settlement.id
93
+
94
+ group_filter = uuid_comparison_exp(_eq=group_id)
95
+ settled_filter = uuid_comparison_exp(_is_null=True)
96
+
97
+ if select is not None:
98
+ where_data = select.model_copy(
99
+ update={
100
+ "group_id": group_filter,
101
+ "settled_id": settled_filter,
102
+ }
103
+ )
104
+ else:
105
+ where_data = eetschema_expense_bool_exp(
106
+ group_id=group_filter,
107
+ settled_id=settled_filter,
108
+ )
109
+
110
+ result = await self._client.settle_unsettled_expenses(
111
+ settlement_id=settlement_id,
112
+ where=where_data,
113
+ )
114
+ settled = transform_settle_unsettled_expenses(result)
115
+
116
+ inserted_adjustments: List[ExpenseFields] = []
117
+ if not do_not_create_adjustment_expenses:
118
+ balances = calculate_balances_from_expenses(settled.expenses)
119
+ adjustments = calculate_adjustment_expenses(balances)
120
+
121
+ now = datetime.now(timezone.utc)
122
+ description = f"Vereffening {now.strftime('%d-%m-%Y')}"
123
+
124
+ async def create_and_update_adjustment(
125
+ adjustment: CalculatedAdjustmentExpense,
126
+ ) -> ExpenseFields:
127
+ distribution_data = [
128
+ eetschema_expense_distribution_insert_input(
129
+ user_id=d["user"].id,
130
+ payed_amount=d["payed_amount"],
131
+ count=d["count"],
132
+ )
133
+ for d in adjustment["expense_distributions"]
134
+ ]
135
+
136
+ created_raw = await self._client.create_expense(
137
+ group_id=group_id,
138
+ payed_by=adjustment["payed_by"].id,
139
+ payed_amount=adjustment["payed_amount"],
140
+ payed_at=now,
141
+ description=description,
142
+ settlement_expense_id=settlement_id,
143
+ data=distribution_data,
144
+ )
145
+ created_expense = transform_create_expense(created_raw)
146
+
147
+ updated_raw = await self._client.update_expense(
148
+ expense_id=created_expense.id,
149
+ set_=eetschema_expense_set_input(settled_id=settlement_id),
150
+ )
151
+ return transform_update_expense(updated_raw)
152
+
153
+ inserted_adjustments = list(
154
+ await asyncio.gather(
155
+ *(create_and_update_adjustment(adj) for adj in adjustments)
156
+ )
157
+ )
158
+
159
+ return {
160
+ "id": settlement_id,
161
+ "expenses": settled.expenses,
162
+ "adjustments": inserted_adjustments,
163
+ }
164
+
165
+ async def expenses(
166
+ self,
167
+ settlement_id: str,
168
+ where: Optional[eetschema_expense_bool_exp] = None,
169
+ order: Optional[list[eetschema_expense_order_by]] = None,
170
+ ) -> list[ExpenseFields]:
171
+ return await self._expenses(
172
+ settlement_id=settlement_id,
173
+ settlement_expense_id_not_null=False,
174
+ where=where,
175
+ order=order,
176
+ )
177
+
178
+ async def adjustments(
179
+ self,
180
+ settlement_id: str,
181
+ where: Optional[eetschema_expense_bool_exp] = None,
182
+ order: Optional[list[eetschema_expense_order_by]] = None,
183
+ ) -> list[ExpenseFields]:
184
+ return await self._expenses(
185
+ settlement_id=settlement_id,
186
+ settlement_expense_id_not_null=True,
187
+ where=where,
188
+ order=order,
189
+ )
190
+
191
+ async def _expenses(
192
+ self,
193
+ settlement_id: str,
194
+ settlement_expense_id_not_null: bool,
195
+ where: Optional[eetschema_expense_bool_exp] = None,
196
+ order: Optional[list[eetschema_expense_order_by]] = None,
197
+ ) -> list[ExpenseFields]:
198
+ deleted_filter = Boolean_comparison_exp(_eq=False)
199
+ settled_filter = uuid_comparison_exp(_eq=settlement_id)
200
+ settlement_expense_filter = uuid_comparison_exp(
201
+ _is_null=not settlement_expense_id_not_null
202
+ )
203
+
204
+ if where is not None:
205
+ where_data = where.model_copy(
206
+ update={
207
+ "deleted": deleted_filter,
208
+ "settled_id": settled_filter,
209
+ "settlement_expense_id": settlement_expense_filter,
210
+ }
211
+ )
212
+ else:
213
+ where_data = eetschema_expense_bool_exp(
214
+ deleted=deleted_filter,
215
+ settled_id=settled_filter,
216
+ settlement_expense_id=settlement_expense_filter,
217
+ )
218
+
219
+ order_data = order or [eetschema_expense_order_by(created_at=order_by.desc)]
220
+
221
+ result = await self._client.settlement_expenses(
222
+ where=where_data,
223
+ order=order_data,
224
+ )
225
+ return transform_settlement_expenses(result)
@@ -0,0 +1,95 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ from pydantic import BaseModel, ConfigDict
5
+
6
+ from eetlijst.generated.create_settlement import CreateSettlement
7
+ from eetlijst.generated.fragments import (
8
+ ExpenseFields,
9
+ SettlementFields,
10
+ SettlementFieldsCreatedBy,
11
+ )
12
+ from eetlijst.generated.settle_unsettled_expenses import SettleUnsettledExpenses
13
+ from eetlijst.generated.settlement_expenses import SettlementExpenses
14
+
15
+ from eetlijst.exceptions import EetlijstException
16
+
17
+ from eetlijst.services.expenses.transformers import transform_expense
18
+
19
+
20
+ class SettlementResult(BaseModel):
21
+ model_config = ConfigDict(frozen=True)
22
+
23
+ id: str
24
+ group_id: str
25
+ created_at: datetime
26
+ updated_at: datetime
27
+ created_by: SettlementFieldsCreatedBy
28
+ expenses_total: Optional[int] = None
29
+ adjustments_total: Optional[int] = None
30
+
31
+
32
+ class SettleUnsettledExpensesResult(BaseModel):
33
+ model_config = ConfigDict(frozen=True)
34
+
35
+ number_expenses: int
36
+ expenses: list[ExpenseFields]
37
+
38
+
39
+ def transform_settlement(settlement: Optional[SettlementFields]) -> SettlementResult:
40
+ if not settlement:
41
+ raise EetlijstException("Settlement not found")
42
+
43
+ expenses_total: Optional[int] = None
44
+ if (
45
+ settlement.expenses_total
46
+ and settlement.expenses_total.aggregate
47
+ and settlement.expenses_total.aggregate.sum
48
+ ):
49
+ expenses_total = settlement.expenses_total.aggregate.sum.payed_amount
50
+
51
+ adjustments_total: Optional[int] = None
52
+ if (
53
+ settlement.adjustments_total
54
+ and settlement.adjustments_total.aggregate
55
+ and settlement.adjustments_total.aggregate.sum
56
+ ):
57
+ adjustments_total = settlement.adjustments_total.aggregate.sum.payed_amount
58
+
59
+ data = {
60
+ **settlement.model_dump(),
61
+ "expenses_total": expenses_total,
62
+ "adjustments_total": adjustments_total,
63
+ }
64
+
65
+ return SettlementResult(**data)
66
+
67
+
68
+ def transform_create_settlement(result: CreateSettlement) -> SettlementResult:
69
+ if not result.settlement:
70
+ raise EetlijstException("Failed to create settlement")
71
+
72
+ return transform_settlement(result.settlement)
73
+
74
+
75
+ def transform_settle_unsettled_expenses(
76
+ result: SettleUnsettledExpenses,
77
+ ) -> SettleUnsettledExpensesResult:
78
+ if not result.update_eetschema_expense:
79
+ raise EetlijstException("Failed to settle expenses")
80
+
81
+ expenses = [
82
+ transform_expense(expense)
83
+ for expense in result.update_eetschema_expense.expenses
84
+ ]
85
+
86
+ data = {
87
+ **result.update_eetschema_expense.model_dump(),
88
+ "expenses": expenses,
89
+ }
90
+
91
+ return SettleUnsettledExpensesResult(**data)
92
+
93
+
94
+ def transform_settlement_expenses(result: SettlementExpenses):
95
+ return [transform_expense(expense) for expense in result.eetschema_expense]
@@ -0,0 +1,84 @@
1
+ from typing import TypedDict
2
+
3
+ from eetlijst.generated.fragments import ExpenseFieldsExpenseDistributionsUser
4
+
5
+ from eetlijst.services.expenses.utils import UserBalance
6
+
7
+
8
+ class AdjustmentExpenseDistribution(TypedDict):
9
+ user: ExpenseFieldsExpenseDistributionsUser
10
+ count: int
11
+ payed_amount: int
12
+ user_id: str
13
+
14
+
15
+ class CalculatedAdjustmentExpense(TypedDict):
16
+ payed_by: ExpenseFieldsExpenseDistributionsUser
17
+ payed_amount: int
18
+ expense_distributions: list[AdjustmentExpenseDistribution]
19
+
20
+
21
+ def calculate_adjustment_expenses(
22
+ balances: list[UserBalance],
23
+ ) -> list[CalculatedAdjustmentExpense]:
24
+ creditors: list[UserBalance] = sorted(
25
+ [
26
+ UserBalance(user=b["user"], balance=b["balance"])
27
+ for b in balances
28
+ if b["balance"] > 0
29
+ ],
30
+ key=lambda b: b["balance"],
31
+ )
32
+
33
+ debtors: list[UserBalance] = sorted(
34
+ [
35
+ UserBalance(user=b["user"], balance=b["balance"])
36
+ for b in balances
37
+ if b["balance"] < 0
38
+ ],
39
+ key=lambda b: abs(b["balance"]),
40
+ reverse=True,
41
+ )
42
+
43
+ results: list[CalculatedAdjustmentExpense] = []
44
+
45
+ for creditor in creditors:
46
+ distributions: list[AdjustmentExpenseDistribution] = []
47
+
48
+ for debtor in debtors:
49
+ amount = min(creditor["balance"], abs(debtor["balance"]))
50
+ if amount <= 0:
51
+ continue
52
+
53
+ distributions.append(
54
+ {
55
+ "user": debtor["user"],
56
+ "payed_amount": amount,
57
+ "user_id": debtor["user"].id,
58
+ "count": 0,
59
+ }
60
+ )
61
+
62
+ creditor["balance"] -= amount
63
+ debtor["balance"] += amount
64
+
65
+ total = -sum(entry["payed_amount"] for entry in distributions)
66
+
67
+ distributions.append(
68
+ {
69
+ "user": creditor["user"],
70
+ "payed_amount": total,
71
+ "user_id": creditor["user"].id,
72
+ "count": 0,
73
+ }
74
+ )
75
+
76
+ results.append(
77
+ {
78
+ "payed_by": creditor["user"],
79
+ "payed_amount": total,
80
+ "expense_distributions": distributions,
81
+ }
82
+ )
83
+
84
+ return results
@@ -0,0 +1 @@
1
+ from .users import Users