Repository: bvanelli/actualpy
Pull request #89 (2024-10-23 14:41:19)

Allow creating transactions without payee
## Overview

`actualpy` fails to create a transaction without a payee.

When a default (payee='') payee value is used, `actualpy` searches for a payee with an empty name and expects to find _only one_ such payee. Actual Budget uses special payees to represent transfers between accounts. Such payees always have an empty name. If a budget has more than one transfer payee, `create_transaction` will fail because `get_or_create_payee` will find multiple payees with empty names and fail validation.

When a `None` payee value is passed, `create_transaction` succeeds, but then `Actual` errors because of a invalid payee (I haven't captured the exact error, but you can easily reproduce this but calling `create_transaction(..., payee=None)`) and then editing this transaction in Actual.

## Solution

This proposal adds support for the `None` payee value in `create_transaction`.
When a `None` value is passed, the implementation does not attempt to create a payee, and passes `None` as a payee_id in `create_transaction_from_ids`.

The proposed implementation should be backward compatible.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/bvanelli/actualpy/pull/89?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Brunno+Vanelli) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 97.67%. Comparing base [(`68d640d`)](https://app.codecov.io/gh/bvanelli/actualpy/commit/68d640dfda36722846c7d01751aac48450891cf4?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Brunno+Vanelli) to head [(`4ff312e`)](https://app.codecov.io/gh/bvanelli/actualpy/commit/4ff312e57d5951ee6972ee600cb2498eec18ff6d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Brunno+Vanelli).

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main      #89   +/-   ##
=======================================
  Coverage   97.67%   97.67%           
=======================================
  Files          16       16           
  Lines        2194     2196    +2     
=======================================
+ Hits         2143     2145    +2     
  Misses         51       51           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/bvanelli/actualpy/pull/89?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Brunno+Vanelli).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Brunno+Vanelli).

bvanelli: Thanks for the PR, actually I don't know why I've set the payee to an empty string initially... I tried on the UI and it's apparently setting it to none, but I guess it was to overcome this issue with the payees linked to the accounts, but your solution is better.

I'll check if all tests run when changing the default and I'll merge it.

Patch:
diff --git a/actual/queries.py b/actual/queries.py
index 7ec6c04..9960497 100644
--- a/actual/queries.py
+++ b/actual/queries.py
@@ -194,7 +194,7 @@ def create_transaction(
     s: Session,
     date: datetime.date,
     account: str | Accounts,
-    payee: str | Payees = "",
+    payee: str | Payees | None = None,
     notes: str | None = "",
     category: str | Categories | None = None,
     amount: decimal.Decimal | float | int = 0,
@@ -209,7 +209,7 @@ def create_transaction(
     :param date: date of the transaction.
     :param account: either account name or account object (via `get_account` or `get_accounts`). Will not be
     auto-created if missing.
-    :param payee: name of the payee from the transaction. Will be created if missing.
+    :param payee: optional name of the payee from the transaction. Will be created if missing.
     :param notes: optional description for the transaction.
     :param category: optional category for the transaction. Will be created if not existing.
     :param amount: amount of the transaction. Positive indicates that the account balance will go up (deposit), and
@@ -229,14 +229,16 @@ def create_transaction(
         imported_payee = imported_payee.strip()
         if not payee:
             payee = imported_payee
-    payee = get_or_create_payee(s, payee)
+    payee_id = None
+    if payee is not None:
+        payee_id = get_or_create_payee(s, payee).id
     if category:
         category_id = get_or_create_category(s, category).id
     else:
         category_id = None
 
     return create_transaction_from_ids(
-        s, date, acct.id, payee.id, notes, category_id, amount, imported_id, cleared, imported_payee
+        s, date, acct.id, payee_id, notes, category_id, amount, imported_id, cleared, imported_payee
     )
 
 


Test patch:
diff --git a/tests/test_database.py b/tests/test_database.py
index 130d75a..3608bd5 100644
--- a/tests/test_database.py
+++ b/tests/test_database.py
@@ -77,6 +77,12 @@ def test_transaction(session):
     assert other.balance == decimal.Decimal("-9.95")
 
 
+def test_transaction_without_payee(session):
+    other = create_account(session, "Other")
+    tr = create_transaction(session, date=date.today(), account=other)
+    assert tr.payee_id is None
+
+
 def test_reconcile_transaction(session):
     today = date.today()
     create_account(session, "Bank")