Skip to content

Commit c5c0788

Browse files
committed
Add transactions
1 parent cf74950 commit c5c0788

File tree

5 files changed

+604
-0
lines changed

5 files changed

+604
-0
lines changed

src/pyfirefly/models.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
from dataclasses import dataclass, field
6+
from typing import Any
67

78
from mashumaro import field_options
89
from mashumaro.mixins.orjson import DataClassORJSONMixin
@@ -84,3 +85,91 @@ class Account(DataClassORJSONMixin):
8485
type: str
8586
id: str
8687
attributes: AccountAttributes
88+
89+
90+
@dataclass
91+
class Transaction(DataClassORJSONMixin): # pylint: disable=too-many-instance-attributes
92+
"""Model for a Firefly transaction."""
93+
94+
user: str | None = None
95+
transaction_journal_id: str | None = None
96+
type: str | None = None
97+
date: str | None = None
98+
order: int | None = None
99+
currency_id: str | None = None
100+
currency_code: str | None = None
101+
currency_symbol: str | None = None
102+
currency_name: str | None = None
103+
currency_decimal_places: int | None = None
104+
foreign_currency_id: str | None = None
105+
foreign_currency_code: str | None = None
106+
foreign_currency_symbol: str | None = None
107+
foreign_currency_decimal_places: int | None = None
108+
amount: str | None = None
109+
foreign_amount: str | None = None
110+
description: str | None = None
111+
source_id: str | None = None
112+
source_name: str | None = None
113+
source_iban: str | None = None
114+
source_type: str | None = None
115+
destination_id: str | None = None
116+
destination_name: str | None = None
117+
destination_iban: str | None = None
118+
destination_type: str | None = None
119+
budget_id: str | None = None
120+
budget_name: str | None = None
121+
category_id: str | None = None
122+
category_name: str | None = None
123+
bill_id: str | None = None
124+
bill_name: str | None = None
125+
reconciled: bool | None = None
126+
notes: str | None = None
127+
tags: list[str] | None = None
128+
internal_reference: str | None = None
129+
external_id: str | None = None
130+
external_url: str | None = None
131+
original_source: str | None = None
132+
recurrence_id: str | None = None
133+
recurrence_total: int | None = None
134+
recurrence_count: int | None = None
135+
bunq_payment_id: str | None = None
136+
import_hash_v2: str | None = None
137+
sepa_cc: str | None = None
138+
sepa_ct_op: str | None = None
139+
sepa_ct_id: str | None = None
140+
sepa_db: str | None = None
141+
sepa_country: str | None = None
142+
sepa_ep: str | None = None
143+
sepa_ci: str | None = None
144+
sepa_batch_id: str | None = None
145+
interest_date: str | None = None
146+
book_date: str | None = None
147+
process_date: str | None = None
148+
due_date: str | None = None
149+
payment_date: str | None = None
150+
invoice_date: str | None = None
151+
latitude: float | None = None
152+
longitude: float | None = None
153+
zoom_level: int | None = None
154+
has_attachments: bool | None = None
155+
156+
157+
@dataclass
158+
class TransactionAttributes(DataClassORJSONMixin):
159+
"""Attributes of a Firefly transaction."""
160+
161+
created_at: str | None = None
162+
updated_at: str | None = None
163+
user: str | None = None
164+
group_title: str | None = None
165+
transactions: list[Transaction] | None = None
166+
167+
168+
@dataclass
169+
class TransactionResource(DataClassORJSONMixin):
170+
"""Model for a Firefly transaction resource."""
171+
172+
type: str
173+
id: str
174+
attributes: TransactionAttributes
175+
links: dict[str, Any] | None = None

src/pyfirefly/pyfirefly.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,55 @@ async def get_accounts(self) -> list[Account]:
182182

183183
return [Account.from_dict(acc) for acc in accounts]
184184

185+
async def get_transactions(
186+
self,
187+
account_id: int | None = None,
188+
start: str | None = None,
189+
end: str | None = None,
190+
) -> list[dict[str, Any]]:
191+
"""Get transactions for a specific account. Else, return all transactions.
192+
193+
Args:
194+
----
195+
account_id: The ID of the account to retrieve transactions for.
196+
start: The start date for the transactions.
197+
end: The end date for the transactions.
198+
199+
Returns:
200+
-------
201+
A list of transactions for the specified account.
202+
203+
"""
204+
transactions: list[dict[str, Any]] = []
205+
next_page: int | None = 1
206+
207+
uri = f"accounts/{account_id}/transactions"
208+
if account_id is None:
209+
uri = "transactions"
210+
211+
while next_page:
212+
params: dict[str, str] = {"page": str(next_page)}
213+
if start:
214+
params["start"] = start
215+
if end:
216+
params["end"] = end
217+
218+
response = await self._request(
219+
uri=uri,
220+
method="GET",
221+
params=params,
222+
)
223+
224+
transactions.extend(response["data"])
225+
226+
pagination = response.get("meta", {}).get("pagination", {})
227+
current_page = int(pagination.get("current_page", 1) or 1)
228+
total_pages = int(pagination.get("total_pages", 1) or 1)
229+
230+
next_page = current_page + 1 if current_page < total_pages else None
231+
232+
return transactions
233+
185234
async def close(self) -> None:
186235
"""Close open client session."""
187236
if self._session and self._close_session:

tests/__snapshots__/test_models.ambr

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,223 @@
22
# name: test_about_model
33
About(version='5.8.0-alpha.1', api_version='5.8.0-alpha.1', php_version='8.1.5', os='Linux', driver='mysql')
44
# ---
5+
# name: test_account_transactions_model
6+
list([
7+
dict({
8+
'attributes': dict({
9+
'created_at': '2018-09-17T12:46:47+01:00',
10+
'group_title': 'Split transaction title.',
11+
'transactions': list([
12+
dict({
13+
'amount': '123.45',
14+
'bill_id': '111',
15+
'bill_name': 'Monthly rent',
16+
'book_date': '2025-06-16T19:01:38.730Z',
17+
'budget_id': '4',
18+
'budget_name': 'Groceries',
19+
'bunq_payment_id': 'string',
20+
'category_id': '43',
21+
'category_name': 'Groceries',
22+
'currency_code': 'EUR',
23+
'currency_decimal_places': 2,
24+
'currency_id': '12',
25+
'currency_name': 'Euro',
26+
'currency_symbol': '$',
27+
'date': '2018-09-17T12:46:47+01:00',
28+
'description': 'Vegetables',
29+
'destination_iban': 'NL02ABNA0123456789',
30+
'destination_id': '2',
31+
'destination_name': 'Buy and Large',
32+
'destination_type': 'Asset account',
33+
'due_date': '2025-06-16T19:01:38.730Z',
34+
'external_id': 'string',
35+
'external_url': 'string',
36+
'foreign_amount': '123.45',
37+
'foreign_currency_code': 'USD',
38+
'foreign_currency_decimal_places': 2,
39+
'foreign_currency_id': '17',
40+
'foreign_currency_symbol': '$',
41+
'has_attachments': False,
42+
'import_hash_v2': 'string',
43+
'interest_date': '2025-06-16T19:01:38.730Z',
44+
'internal_reference': 'string',
45+
'invoice_date': '2025-06-16T19:01:38.730Z',
46+
'latitude': 51.983333,
47+
'longitude': 5.916667,
48+
'notes': 'Some example notes',
49+
'order': 0,
50+
'original_source': 'string',
51+
'payment_date': '2025-06-16T19:01:38.730Z',
52+
'process_date': '2025-06-16T19:01:38.730Z',
53+
'reconciled': False,
54+
'recurrence_count': 12,
55+
'recurrence_id': 'string',
56+
'recurrence_total': 0,
57+
'sepa_batch_id': 'string',
58+
'sepa_cc': 'string',
59+
'sepa_ci': 'string',
60+
'sepa_country': 'string',
61+
'sepa_ct_id': 'string',
62+
'sepa_ct_op': 'string',
63+
'sepa_db': 'string',
64+
'sepa_ep': 'string',
65+
'source_iban': 'NL02ABNA0123456789',
66+
'source_id': '2',
67+
'source_name': 'Checking account',
68+
'source_type': 'Asset account',
69+
'tags': None,
70+
'transaction_journal_id': '10421',
71+
'type': 'withdrawal',
72+
'user': '3',
73+
'zoom_level': 6,
74+
}),
75+
dict({
76+
'amount': '200.00',
77+
'bill_id': '112',
78+
'bill_name': 'Monthly salary',
79+
'book_date': '2019-01-01T10:00:00+01:00',
80+
'budget_id': '5',
81+
'budget_name': 'Income',
82+
'bunq_payment_id': 'bunq-jan-2019',
83+
'category_id': '44',
84+
'category_name': 'Salary',
85+
'currency_code': 'USD',
86+
'currency_decimal_places': 2,
87+
'currency_id': '13',
88+
'currency_name': 'US Dollar',
89+
'currency_symbol': '$',
90+
'date': '2019-01-01T10:00:00+01:00',
91+
'description': 'Salary',
92+
'destination_iban': 'NL02ABNA0123456789',
93+
'destination_id': '2',
94+
'destination_name': 'Checking account',
95+
'destination_type': 'Asset account',
96+
'due_date': '2019-01-01T10:00:00+01:00',
97+
'external_id': 'ext-jan-2019',
98+
'external_url': 'https://employer.com/payroll',
99+
'foreign_amount': '180.00',
100+
'foreign_currency_code': 'EUR',
101+
'foreign_currency_decimal_places': 2,
102+
'foreign_currency_id': '18',
103+
'foreign_currency_symbol': '€',
104+
'has_attachments': True,
105+
'import_hash_v2': 'hash-jan-2019',
106+
'interest_date': '2019-01-01T10:00:00+01:00',
107+
'internal_reference': 'ref-jan-2019',
108+
'invoice_date': '2019-01-01T10:00:00+01:00',
109+
'latitude': 40.7128,
110+
'longitude': -74.006,
111+
'notes': 'Salary for January',
112+
'order': 1,
113+
'original_source': 'payroll',
114+
'payment_date': '2019-01-01T10:00:00+01:00',
115+
'process_date': '2019-01-01T10:00:00+01:00',
116+
'reconciled': True,
117+
'recurrence_count': 1,
118+
'recurrence_id': 'rec-jan-2019',
119+
'recurrence_total': 1,
120+
'sepa_batch_id': 'sepa-batch-jan-2019',
121+
'sepa_cc': 'sepa-cc-jan-2019',
122+
'sepa_ci': 'sepa-ci-jan-2019',
123+
'sepa_country': 'US',
124+
'sepa_ct_id': 'sepa-id-jan-2019',
125+
'sepa_ct_op': 'sepa-op-jan-2019',
126+
'sepa_db': 'sepa-db-jan-2019',
127+
'sepa_ep': 'sepa-ep-jan-2019',
128+
'source_iban': 'US12345678901234567890',
129+
'source_id': '3',
130+
'source_name': 'Employer',
131+
'source_type': 'Income account',
132+
'tags': list([
133+
'income',
134+
'salary',
135+
]),
136+
'transaction_journal_id': '10422',
137+
'type': 'deposit',
138+
'user': '4',
139+
'zoom_level': 8,
140+
}),
141+
dict({
142+
'amount': '75.00',
143+
'bill_id': '113',
144+
'bill_name': 'Monthly transfer',
145+
'book_date': '2020-05-15T15:30:00+01:00',
146+
'budget_id': '6',
147+
'budget_name': 'Savings',
148+
'bunq_payment_id': 'bunq-may-2020',
149+
'category_id': '45',
150+
'category_name': 'Transfers',
151+
'currency_code': 'GBP',
152+
'currency_decimal_places': 2,
153+
'currency_id': '14',
154+
'currency_name': 'Pound Sterling',
155+
'currency_symbol': '£',
156+
'date': '2020-05-15T15:30:00+01:00',
157+
'description': 'Transfer to savings',
158+
'destination_iban': 'GB29NWBK60161331926819',
159+
'destination_id': '4',
160+
'destination_name': 'Savings account',
161+
'destination_type': 'Asset account',
162+
'due_date': '2020-05-15T15:30:00+01:00',
163+
'external_id': 'ext-may-2020',
164+
'external_url': 'https://bank.com/transfer',
165+
'foreign_amount': '100.00',
166+
'foreign_currency_code': 'USD',
167+
'foreign_currency_decimal_places': 2,
168+
'foreign_currency_id': '19',
169+
'foreign_currency_symbol': '$',
170+
'has_attachments': False,
171+
'import_hash_v2': 'hash-may-2020',
172+
'interest_date': '2020-05-15T15:30:00+01:00',
173+
'internal_reference': 'ref-may-2020',
174+
'invoice_date': '2020-05-15T15:30:00+01:00',
175+
'latitude': 51.5074,
176+
'longitude': 0.1278,
177+
'notes': 'Transfer to savings account',
178+
'order': 2,
179+
'original_source': 'bank',
180+
'payment_date': '2020-05-15T15:30:00+01:00',
181+
'process_date': '2020-05-15T15:30:00+01:00',
182+
'reconciled': False,
183+
'recurrence_count': 1,
184+
'recurrence_id': 'rec-may-2020',
185+
'recurrence_total': 1,
186+
'sepa_batch_id': 'sepa-batch-may-2020',
187+
'sepa_cc': 'sepa-cc-may-2020',
188+
'sepa_ci': 'sepa-ci-may-2020',
189+
'sepa_country': 'GB',
190+
'sepa_ct_id': 'sepa-id-may-2020',
191+
'sepa_ct_op': 'sepa-op-may-2020',
192+
'sepa_db': 'sepa-db-may-2020',
193+
'sepa_ep': 'sepa-ep-may-2020',
194+
'source_iban': 'GB29NWBK60161331926819',
195+
'source_id': '2',
196+
'source_name': 'Checking account',
197+
'source_type': 'Asset account',
198+
'tags': list([
199+
'transfer',
200+
]),
201+
'transaction_journal_id': '10423',
202+
'type': 'transfer',
203+
'user': '5',
204+
'zoom_level': 10,
205+
}),
206+
]),
207+
'updated_at': '2018-09-17T12:46:47+01:00',
208+
'user': '3',
209+
}),
210+
'id': '2',
211+
'links': dict({
212+
'0': dict({
213+
'rel': 'self',
214+
'uri': '/OBJECTS/1',
215+
}),
216+
'self': 'https://demo.firefly-iii.org/api/v1/OBJECTS/1',
217+
}),
218+
'type': 'transactions',
219+
}),
220+
])
221+
# ---
5222
# name: test_accounts_model
6223
list([
7224
Account(type='accounts', id='2', attributes=AccountAttributes(created_at='2018-09-17T12:46:47+01:00', updated_at='2018-09-17T12:46:47+01:00', active=False, name='My checking account', type='asset', account_role='defaultAsset', currency_id='12', currency_code='EUR', currency_symbol='$', currency_decimal_places=2, native_currency_id='12', native_currency_code='EUR', native_currency_symbol='$', native_currency_decimal_places=2, current_balance='123.45', native_current_balance='123.45', current_balance_date='2018-09-17T12:46:47+01:00', order=1, notes='Some example notes', monthly_payment_date='2018-09-17T12:46:47+01:00', credit_card_type='monthlyFull', account_number='7009312345678', iban='GB98MIDL07009312345678', bic='BOFAUS3N', virtual_balance='123.45', native_virtual_balance='123.45', opening_balance='-1012.12', native_opening_balance='-1012.12', opening_balance_date='2018-09-17T12:46:47+01:00', liability_type='loan', liability_direction='credit', interest='5.3', interest_period='monthly', current_debt='1012.12', include_net_worth=True, longitude=5.916667, latitude=51.983333, zoom_level=6, last_activity=None)),

0 commit comments

Comments
 (0)