Transaction Rules
Transaction Rules are a powerful feature in WYGIWYH that allow for the automatic execution of actions based on transaction events. This can be used to modify the transaction that triggered the event, or to create/update other transactions, helping you save time and ensure consistency in your financial tracking.
A rule is composed of a Trigger and one or more Actions.
Rule Configuration
Each rule has the following configuration options:
- Name & Description: To identify the rule.
- Active: A rule will only run if it's active.
- Triggering Events:
Run on creation: The rule will run when a new transaction is created.Run on update: The rule will run when an existing transaction is updated.Run on delete: The rule will run when a transaction is deleted.
- Sequenced: A sequenced rule will update its variables between each rule execution, meaning that the next action will have access to the values of the previous action instead of the originating transaction.
- Trigger (the
If...part): A condition that must evaluate toTruefor the rule's actions to be executed.
Actions
When a rule's trigger condition is met, it will execute one or more actions. There are two types of actions:
- Edit Transaction: Modifies the transaction that triggered the rule.
- Update or Create Transaction: Finds a different transaction to update, or creates a new one if no matching transaction is found.
Action Order
Since v.0.17, each action has an Order field (a number, defaulting to 0). There's two execution modes based on this field:
-
If all actions in a rule have an order number of 0, "Edit Transaction" actions are executed before "Update or Create Transaction" actions, both ordered by their creation date.
-
If one or more actions in a rule have an order number higher than 0, actions are executed in the given order, with creation date being the tiebreaker.
Edit Transaction Action
This action modifies the fields of the transaction that triggered the rule.
You must select a Field to Set and provide a Value. The value is an expression that will be evaluated to determine the new value for the field.
The following fields can be modified:
- Account
int,str,List[int, str] - Type
"EX" or "IN" - Paid
bool - Mute
bool - Date
datetime.date - Reference Date
datetime.date - Amount
Positive Decimal,int,str (still needs to be a valid number,float - Description
str | None - Notes
str | None - Internal ID
str | None - Internal Note
str | None - Category
int,str,List[int, str] - Tags
int,str,List[int, str] - Entities
int,str,List[int, str]
Int and str
When a field accepts a int or str, this means it either accepts an ID of an existing object (e.g. the ID of an account) or the name of an existing account as a string.
Creating new objects
Unlike other interfaces, rules do not create inexistent objects like categories and tags. Make sure anything you set exists beforehand.
Update or Create Transaction Action
This action is more advanced. It allows you to define search criteria to find a specific transaction.
- If one or more transactions match the criteria, the most recent one will be updated.
- If no transaction matches, a new one will be created.
This action has three main parts:
- Filter: An optional expression that, if it evaluates to
False, will prevent this specific action from running. - Search Criteria: A set of conditions to find the target transaction. For each field (e.g.,
Description,Amount), you can provide a value and an operator (e.g.,contains,is exactly,greater than). - Set Values: The new values for the fields of the found or created transaction. If a field is left blank, it will not be changed on an update (it will be left empty on creation).
The Evaluation Engine
The Trigger, action Values, and Filter fields all use a limited subset of Python for their expressions, powered by SimpleEval. You can use variables and functions to create dynamic logic.
Caveats
-
Since you're writing Python, make sure to quote your strings (e.g.
"My account"instead of justMy account) -
You are creating big one-liners of python code, you can't define variables or call functions outside the available scope.
Available Variables
The following variables are available, referring to the transaction that triggered the rule.
| Variable | Description |
|---|---|
id |
The ID of the transaction. |
account_name |
The name of the transaction's account. |
account_id |
The ID of the transaction's account. |
account_group_name |
The name of the account's group. |
account_group_id |
The ID of the account's group. |
is_asset_account |
True if the account is an asset account. |
is_archived_account |
True if the account is archived. |
category_name |
The name of the transaction's category. |
category_id |
The ID of the transaction's category. |
tag_names |
A list of tag names (e.g., ['Work', 'Personal']). |
tag_ids |
A list of tag IDs (e.g., [1, 5]). |
entities_names |
A list of entity names. |
entities_ids |
A list of entity IDs. |
is_expense |
True if the transaction is an expense. |
is_income |
True if the transaction is an income. |
is_paid |
True if the transaction is marked as paid. |
description |
The transaction's description. |
amount |
The transaction's amount (as a Decimal). |
notes |
The transaction's notes. |
date |
The transaction's date (as a datetime object). |
reference_date |
The transaction's reference date (as a datetime object). |
internal_note |
The transaction's internal note. |
internal_id |
The transaction's internal ID. |
is_deleted |
True if the transaction is soft-deleted. |
is_muted |
True if the transaction is muted. |
is_recurring |
True if the transaction is a recurring transaction. |
is_installment |
True if the transaction is part of an installment plan. |
installment_number |
The installment number of this transaction, if the transaction is part of an installment plan. Else None |
installment_total |
The number of installments of the installment plan this transaction is part of. Else None |
Always-available Variables
These variables are always available
| Variable | Description |
|---|---|
is_on_create |
If the running rule has been triggered by a create event |
is_on_update |
If the running rule has been triggered by an update event |
is_on_delete |
If the running rule has been triggered by a delete event |
Context-Specific Variables:
- On Update (
on_update): When a rule is triggered by an update, you can access the previous values of the transaction using theold_prefix (e.g.,old_amount,old_description). - Inside "Update or Create" Actions: Within the
Set ValuesandFilterfields of an "Update or Create" action, you can access the fields of the transaction that was found by the search criteria using themy_prefix (e.g.,my_amount,my_description).
Available Functions
| Function | Description |
|---|---|
relativedelta |
From dateutil, used to add or subtract time from dates (e.g., date + relativedelta(months=1)). |
datetime, date |
Standard Python objects for working with dates and times. |
str, int, float, Decimal |
Standard Python functions for type casting. |
transactions(<filter>) |
A special function to query other transactions. Returns a TransactionsGetter object. |
abs(x) |
Returns the absolute value of x |
random() |
Return the next random floating-point number in the range 0.0 <= X < 1.0 |
randint(a, b) |
Return a random integer N such that a <= N <= b |
Note
Feel free to open an issue if you need some other function available
The transactions() function
This function allows you to aggregate data from other transactions within a rule.
transactions(filter)
The filter uses Django's ORM filter syntax (e.g., account__id=5, description__contains='Coffee').
The returned object has the following properties:
.sum: The sum of theamountfor the filtered transactions..balance: The absolute balance (total income - total expenses), always a positive number..raw_balance: The raw balance (can be negative)..exclude(filter): Exclude one or mode results (e.g.description='Coffee'will exclude any transaction with the description 'Coffee' from the original filter),
Example: transactions(account_id=account_id, date__month=date.month).sum returns the sum of all transactions in the same account and month as the triggering transaction.
Testing
You can test your rule by clicking on the Test button. Testing works by running the current rule against an existing transaction, all changes and updates are ran on the database and then rolled back (using an atomic transaction).
Known Issues
- Since we're creating the transactions even if rolling them back afterwards, the transactions() function may return incorrect data during testing, on normal execution it should work correctly.
Examples
Example 1: Add a tag for investment income
Add the "Yield" tag to any income transaction in an investment account.
- Trigger:
account_name == "My Investing Account" and is_income - Action (Edit Transaction):
- Set field:
Tags - To value:
tag_names + ["Yield"]
- Set field:
Example 2: Adjust credit card reference date
Move credit card transactions to the next month's budget if they occur after the statement's cutoff date (e.g., the 26th).
- Trigger:
account_name == "My credit card" and date.day >= 26 and reference_date.month == date.month - Action (Edit Transaction):
- Set field:
Reference Date - To value:
(reference_date + relativedelta(months=1)).replace(day=1)
- Set field:
Example 3: Create a fee transaction when a specific transaction is deleted
When a "Subscription A" transaction is deleted, create a new "Cancellation Fee" transaction for $5.
- Rule:
- Run on delete: Enabled
- Trigger:
description == "Subscription A"
- Action (Update or Create Transaction):
- Search Criteria: (Leave all blank to always create a new transaction)
- Set Values:
Account:account_id(use the same account as the deleted transaction)Description:'Cancellation Fee for Subscription A'Amount:Decimal("5.00")Type:'EX'(Expense)
Example 4: Update a "Goal" transaction
Imagine you have a recurring transaction for a savings goal. When you add a manual contribution, you want to update the goal transaction's notes.
- Rule:
- Run on create: Enabled
- Trigger:
description.startswith("Manual contribution to goal:")
- Action (Update or Create Transaction):
- Search Criteria:
Descriptionis exactlyMy Savings GoalReference Dateis exactlydate.replace(day=1)(find the goal transaction for the current month)
- Set Values:
Notes:my_notes + f"\n- Manual contribution of ${amount} on {date.strftime('%Y-%m-%d')}"
- Search Criteria: