From 1b3d4109de656330241801513123ff328e63ec14 Mon Sep 17 00:00:00 2001 From: Herculino Trotta Date: Sun, 31 Aug 2025 23:15:25 -0300 Subject: [PATCH] Updated Transaction Rules (markdown) --- Transaction-Rules.md | 247 +++++++++++++++++++++++++++++++++---------- 1 file changed, 193 insertions(+), 54 deletions(-) diff --git a/Transaction-Rules.md b/Transaction-Rules.md index a5c3203..5c616ed 100644 --- a/Transaction-Rules.md +++ b/Transaction-Rules.md @@ -1,72 +1,211 @@ ### Transaction Rules -Transaction Rules are a powerful feature in WYGIWYH that allow for automatic modification of transactions based on specified criteria. This can save time and ensure consistency in your financial tracking. +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. -Key Aspects of Transaction Rules: +A rule is composed of a **Trigger** and one or more **Actions**. -* **Conditions**: Set specific criteria that a transaction must meet for the rule to apply. This can include attributes like description, amount, account, etc. -* **Actions**: Define what changes should be made to a transaction when the conditions are met. This can include setting categories, tags, or modifying other fields. -* **Activation Options**: Rules can be set to apply when transactions are created, updated, or both. +#### Rule Configuration -#### Actions and Conditions +Each rule has the following configuration options: -When creating a new rule, you will need to add a Condition and, later, Actions. +* **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](#available-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**: A condition that must evaluate to `True` for the rule's actions to be executed. -Both use a limited subset of Python, via [SimpleEval](https://github.com/danthedeckie/simpleeval). +#### Actions -The Condition must evaluate to True or False, and the Action must evaluate to a value that will be set on the selected field. +When a rule's trigger condition is met, it will execute one or more actions. There are two types of actions: -You may use any of the available [variables](#available-variables) and [functions](#available-functions). +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. -#### Available variables +**Action Order** -* `account_name` -* `account_id` -* `account_group_name` -* `account_group_id` -* `is_asset_account` -* `is_archived_account` -* `category_name` -* `category_id` -* `tag_names` -* `tag_ids` -* `entities_names` -* `entities_ids` -* `is_expense` -* `is_income` -* `is_paid` -* `description` -* `amount` -* `notes` -* `date` -* `reference_date` +Since v.0.17, each action has an `Order` field (a number, defaulting to 0). There's two execution modes based on this field: -#### Available functions +- 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. -* `relativedelta` - -#### Examples - -Add a tag to an income transaction if it happens in a specific account - -``` -If... -account_name == "My Investing Account" and is_income - -Then... -Set Tags to -tag_names + ["Yield"] -``` +- 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. --- -Move credit card transactions to next month when they happen at a cutoff date +### 1. Edit Transaction Action -``` -If... -account_name == "My credit card" and date.day >= 26 and reference_date.month == date.month +This action modifies the fields of the transaction that triggered the rule. -Then... -Set Reference Date to -reference_date + relativedelta(months=1)).replace(day=1) -``` \ No newline at end of file +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` +* 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. + +--- + +### 2. 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 `Value`s, and `Filter` fields all use a limited subset of Python for their expressions, powered by [SimpleEval](https://github.com/danthedeckie/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. | + +**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()` | 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). +* `.raw_balance`: The raw balance (can be negative). + +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. + + +### 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')}"`