Skip to content

Commit 36c3bae

Browse files
Check if financial accounts exist on dry-run.
1 parent bebbc1b commit 36c3bae

12 files changed

Lines changed: 218 additions & 139 deletions

File tree

django/finance/accounting/book.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ def get_transaction(self, transaction_id):
7272
def delete_transaction(self, transaction_id):
7373
raise NotImplementedError
7474

75+
def account_exists(self, account: Account):
76+
raise NotImplementedError
77+
7578
def save(self):
7679
raise NotImplementedError
7780

@@ -165,6 +168,10 @@ def delete_transaction(self, transaction_id):
165168
backend_id = self.get_backend_id(transaction_id)
166169
del self._db[backend_id]
167170

171+
def account_exists(self, account: Account):
172+
# DummyBook accounts always exist
173+
return True
174+
168175
def save(self):
169176
for transaction in self._db.values():
170177
transaction["saved"] = True

django/finance/accounting/cashctrl.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ def get_cct_account(self, account_nbr):
197197

198198
# Check cache first
199199
if account_nbr in self._account_cache:
200+
if self._account_cache[account_nbr] is None:
201+
raise KeyError(f"Account with number {account_nbr} not found in CashCtrl.")
200202
return self._account_cache[account_nbr]
201203

202204
# Build filter as the CashCtrl REST API expects and URL-encode it
@@ -219,7 +221,8 @@ def get_cct_account(self, account_nbr):
219221
return acct.get("id")
220222

221223
# TODO Throw exception if account not found
222-
raise ValueError(f"Account with number {account_nbr} not found in CashCtrl.")
224+
self._account_cache[account_nbr] = None
225+
raise KeyError(f"Account with number {account_nbr} not found in CashCtrl.")
223226

224227
def get_cct_transaction(self, transaction_id):
225228
# Call _get_api_url + journal/read.json?id=[transaction_id] and fetch the transaction
@@ -322,6 +325,14 @@ def delete_transaction(self, transaction_id, autosave=True):
322325
if autosave:
323326
self.save()
324327

328+
def account_exists(self, account: Account):
329+
self._open_book_transactional()
330+
try:
331+
self._book_transaction.get_cct_account(account)
332+
except KeyError:
333+
return False
334+
return True
335+
325336
def save(self):
326337
if not self._book_transaction:
327338
return False

django/finance/accounting/gnucash.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ def delete_transaction(self, transaction_id):
6868
self._book.delete(gnc_transaction)
6969
self.save()
7070

71+
def account_exists(self, account: Account):
72+
try:
73+
self._get_gnc_account(account)
74+
except KeyError:
75+
return False
76+
return True
77+
7178
def save(self):
7279
if not self._book:
7380
return False

django/finance/tests/test_accounting.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,11 @@ def test_delete_transaction(self):
315315
self.assertEqual(book._save_transactions, False)
316316
self.assertEqual(book.delete_transaction("invalid"), None)
317317

318+
def test_account_exists(self):
319+
DummyBook.dummy_db = {}
320+
with AccountingManager() as book:
321+
self.assertTrue(book.account_exists(Account("Test", "1000")))
322+
318323
def test_save(self):
319324
DummyBook.dummy_db = {}
320325
with AccountingManager() as book:

django/finance/tests/test_accounting_cct.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,9 @@ def fetch_account_responses(url, **kw):
188188
},
189189
status_code=200,
190190
)
191-
return Exception("Unknown account number in URL")
191+
elif "123456789" in str(url):
192+
# Simulate unknown account
193+
return Mock(json=lambda: {"total": 0, "data": []}, status_code=200)
192194
return Mock(
193195
json=lambda: {
194196
"success": False,
@@ -557,3 +559,13 @@ def test_process_api_error(self, mock_post, mock_get):
557559
"Test error processing",
558560
autosave=False,
559561
)
562+
563+
@patch("finance.accounting.cashctrl.requests.get")
564+
def test_account_exists(self, mock_get):
565+
with AccountingManager() as book:
566+
# configure fake responses
567+
mock_get.return_value.raise_for_status.side_effect = None
568+
mock_get.side_effect = self.fetch_account_responses
569+
570+
self.assertTrue(book.account_exists(self.account1))
571+
self.assertFalse(book.account_exists(Account("Invalid", "123456789")))

django/finance/tests/test_accounting_gnc.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,8 @@ def test_add_split_transaction_and_autosave(
9393
Transaction(splits, date="2020-01-02", description="Split Transaction Test"),
9494
)
9595
mock_save.assert_called_once()
96+
97+
def test_account_exists(self):
98+
with AccountingManager() as book:
99+
self.assertTrue(book.account_exists(Account("Test", "1000")))
100+
self.assertFalse(book.account_exists(Account("Test", "123456789")))

django/geno/billing.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
## For QR bill and svg -> pdf
2222
from qrbill import QRBill
2323
from reportlab.graphics import renderPDF
24+
from requests import HTTPError
2425
from stdnum.ch import esr
2526
from svglib.svglib import svg2rlg
2627

@@ -98,9 +99,14 @@ def create_invoices(
9899
all_placeholder_invoices = []
99100
all_email_messages = []
100101

101-
with AccountingManager(messages) as book:
102+
book_messages = []
103+
with AccountingManager(book_messages) as book:
102104
if not book:
103-
return messages
105+
contracts = [] # Can't proceed without an accounting book
106+
for msg in book_messages:
107+
messages.append(
108+
CohivaAdminViewMixin.make_response_item(msg, variant=ResponseVariant.ERROR)
109+
)
104110
for contract in contracts:
105111
## Create monthly invoices starting from the reference date
106112
options = {
@@ -124,7 +130,7 @@ def create_invoices(
124130
)
125131
messages.append(
126132
CohivaAdminViewMixin.make_response_item(
127-
_("VERARBEITUNG ABGEBROCHEN!"), variant=ResponseVariant.ERROR
133+
_("VERARBEITUNG ABGEBROCHEN!"), variant=ResponseVariant.WARNING
128134
)
129135
)
130136
break
@@ -780,6 +786,20 @@ def add_invoice_obj(
780786
raise InvoiceCreationError(f"Invoice type {invoice_type} is not implemented.")
781787

782788
if dry_run:
789+
# Just check if the accounts exist
790+
try:
791+
if not book.account_exists(acc_debit):
792+
raise InvoiceCreationError(
793+
_("Account {account} does not exist.").format(account=acc_debit)
794+
)
795+
if not book.account_exists(acc_credit):
796+
raise InvoiceCreationError(
797+
_("Account {account} does not exist.").format(account=acc_credit)
798+
)
799+
except HTTPError as e:
800+
raise InvoiceCreationError(
801+
_("Can't access accounting backend: {error}").format(error=e)
802+
)
783803
return None
784804

785805
if transaction_id:

django/geno/tests/test_billing.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,9 @@ def test_create_invoices_contract_without_address(self):
9898
contract.rental_units.set([self.rentalunits[3]])
9999
messages = geno.billing.create_invoices(reference_date=datetime.date(2001, 6, 1))
100100
self.assertIn("Vertrag hat keine Vertragspartner/Adresse", messages[0]["info"])
101+
self.assertEqual(messages[0]["variant"], "error")
101102
self.assertEqual(messages[1]["info"], "VERARBEITUNG ABGEBROCHEN!")
102-
self.assertEqual(messages[1]["variant"], "error")
103+
self.assertEqual(messages[1]["variant"], "warning")
103104
self.assertEqual(messages[-1]["info"], "3 Rechnungen für 1 Vertrag")
104105

105106
@patch("geno.billing.add_invoice", wraps=add_invoice_exception_generator)

django/geno/transaction_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def get_manual_transaction_types():
3434
for year in range(now.year + 1, geno_settings.TRANSACTION_MEMBERFEE_STARTYEAR - 1, -1):
3535
transaction_types.append((f"fee{year}", f"Mitgliederbeitrag {year}"))
3636

37-
if settings.GENO_ID == "Warmbaechli":
37+
if settings.GENO_ID == "Warmbaechli" or (settings.DEMO and settings.DEBUG):
3838
transaction_types.append(("entry_as", "Beitrittsgebühr+Anteilschein(e) (Post)"))
3939
transaction_types.append(("share_as", "Anteilscheine (Bank)"))
4040
transaction_types.append(

django/geno/views.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3666,7 +3666,6 @@ def post(self, request, *args, **kwargs):
36663666
form = self.get_form()
36673667
formset = self.get_formset()
36683668
if form.is_valid() and formset.is_valid():
3669-
print("Form valid")
36703669
ret = self.process(form, formset)
36713670
if isinstance(ret, FileResponse):
36723671
return ret
@@ -3839,7 +3838,6 @@ def process(self, form, formset):
38393838
"Bitte eingeben oder Betrag löschen.",
38403839
)
38413840
self.error_flag = True
3842-
print("ERROR")
38433841
break
38443842
line["date"] = line["date"].strftime("%d.%m.%Y")
38453843
line["total"] = nformat(line["amount"])
@@ -3954,7 +3952,6 @@ def process(self, form, formset):
39543952
)
39553953
self.error_flag = True
39563954
else:
3957-
print("Output")
39583955
pdf_file = open("/tmp/%s" % output_filename, "rb")
39593956
resp = FileResponse(pdf_file, content_type="application/pdf")
39603957
resp["Content-Disposition"] = "attachment; filename=%s" % output_filename
@@ -4021,7 +4018,7 @@ def get_item_count(self):
40214018
"""Extract invoice count from the results."""
40224019
if self.result and len(self.result) > 0:
40234020
last_section = self.result[-1]
4024-
if last_section.get("objects"):
4021+
if isinstance(last_section, dict) and last_section.get("objects"):
40254022
objects = last_section["objects"]
40264023
if objects and isinstance(objects[-1], str) and "Rechnungen" in objects[-1]:
40274024
invoice_count_text = objects[-1]

0 commit comments

Comments
 (0)