Clone
8
Transaction Rules
Herculino Trotta edited this page 2025-12-14 11:33:17 -03:00

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 to True for 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:

  1. Edit Transaction: Modifies the transaction that triggered the rule.
  2. 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 just My 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 the old_ prefix (e.g., old_amount, old_description).
  • Inside "Update or Create" Actions: Within the Set Values and Filter fields of an "Update or Create" action, you can access the fields of the transaction that was found by the search criteria using the my_ 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 the amount for 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"]

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)

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:
      • Description is exactly My Savings Goal
      • Reference Date is exactly date.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')}"