mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-04-25 01:58:54 +02:00
@@ -65,10 +65,11 @@ class TransactionRuleForm(forms.ModelForm):
|
|||||||
class TransactionRuleActionForm(forms.ModelForm):
|
class TransactionRuleActionForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TransactionRuleAction
|
model = TransactionRuleAction
|
||||||
fields = ("value", "field")
|
fields = ("value", "field", "order")
|
||||||
labels = {
|
labels = {
|
||||||
"field": _("Set field"),
|
"field": _("Set field"),
|
||||||
"value": _("To"),
|
"value": _("To"),
|
||||||
|
"order": _("Order"),
|
||||||
}
|
}
|
||||||
widgets = {"field": TomSelect(clear_button=False)}
|
widgets = {"field": TomSelect(clear_button=False)}
|
||||||
|
|
||||||
@@ -82,6 +83,7 @@ class TransactionRuleActionForm(forms.ModelForm):
|
|||||||
self.helper.form_method = "post"
|
self.helper.form_method = "post"
|
||||||
# TO-DO: Add helper with available commands
|
# TO-DO: Add helper with available commands
|
||||||
self.helper.layout = Layout(
|
self.helper.layout = Layout(
|
||||||
|
"order",
|
||||||
"field",
|
"field",
|
||||||
"value",
|
"value",
|
||||||
)
|
)
|
||||||
@@ -150,6 +152,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
|
"order": _("Order"),
|
||||||
"search_account_operator": _("Operator"),
|
"search_account_operator": _("Operator"),
|
||||||
"search_type_operator": _("Operator"),
|
"search_type_operator": _("Operator"),
|
||||||
"search_is_paid_operator": _("Operator"),
|
"search_is_paid_operator": _("Operator"),
|
||||||
@@ -200,6 +203,7 @@ class UpdateOrCreateTransactionRuleActionForm(forms.ModelForm):
|
|||||||
self.helper.form_method = "post"
|
self.helper.form_method = "post"
|
||||||
|
|
||||||
self.helper.layout = Layout(
|
self.helper.layout = Layout(
|
||||||
|
"order",
|
||||||
BS5Accordion(
|
BS5Accordion(
|
||||||
AccordionGroup(
|
AccordionGroup(
|
||||||
_("Search Criteria"),
|
_("Search Criteria"),
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Generated by Django 5.2 on 2025-08-30 18:48
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("rules", "0014_alter_transactionrule_owner_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="transactionruleaction",
|
||||||
|
options={
|
||||||
|
"ordering": ["order"],
|
||||||
|
"verbose_name": "Edit transaction action",
|
||||||
|
"verbose_name_plural": "Edit transaction actions",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="updateorcreatetransactionruleaction",
|
||||||
|
options={
|
||||||
|
"ordering": ["order"],
|
||||||
|
"verbose_name": "Update or create transaction action",
|
||||||
|
"verbose_name_plural": "Update or create transaction actions",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="transactionruleaction",
|
||||||
|
name="order",
|
||||||
|
field=models.PositiveIntegerField(default=0, verbose_name="Order"),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="updateorcreatetransactionruleaction",
|
||||||
|
name="order",
|
||||||
|
field=models.PositiveIntegerField(default=0, verbose_name="Order"),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -51,6 +51,7 @@ class TransactionRuleAction(models.Model):
|
|||||||
verbose_name=_("Field"),
|
verbose_name=_("Field"),
|
||||||
)
|
)
|
||||||
value = models.TextField(verbose_name=_("Value"))
|
value = models.TextField(verbose_name=_("Value"))
|
||||||
|
order = models.PositiveIntegerField(default=0, verbose_name=_("Order"))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.rule} - {self.field} - {self.value}"
|
return f"{self.rule} - {self.field} - {self.value}"
|
||||||
@@ -59,6 +60,11 @@ class TransactionRuleAction(models.Model):
|
|||||||
verbose_name = _("Edit transaction action")
|
verbose_name = _("Edit transaction action")
|
||||||
verbose_name_plural = _("Edit transaction actions")
|
verbose_name_plural = _("Edit transaction actions")
|
||||||
unique_together = (("rule", "field"),)
|
unique_together = (("rule", "field"),)
|
||||||
|
ordering = ["order"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def action_type(self):
|
||||||
|
return "edit_transaction"
|
||||||
|
|
||||||
|
|
||||||
class UpdateOrCreateTransactionRuleAction(models.Model):
|
class UpdateOrCreateTransactionRuleAction(models.Model):
|
||||||
@@ -290,10 +296,16 @@ class UpdateOrCreateTransactionRuleAction(models.Model):
|
|||||||
verbose_name=_("Tags"),
|
verbose_name=_("Tags"),
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
order = models.PositiveIntegerField(default=0, verbose_name=_("Order"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Update or create transaction action")
|
verbose_name = _("Update or create transaction action")
|
||||||
verbose_name_plural = _("Update or create transaction actions")
|
verbose_name_plural = _("Update or create transaction actions")
|
||||||
|
ordering = ["order"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def action_type(self):
|
||||||
|
return "update_or_create_transaction"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Update or create transaction action for {self.rule}"
|
return f"Update or create transaction action for {self.rule}"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import decimal
|
|||||||
import logging
|
import logging
|
||||||
from datetime import datetime, date
|
from datetime import datetime, date
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
from cachalot.api import cachalot_disabled
|
from cachalot.api import cachalot_disabled
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
@@ -87,7 +88,9 @@ def check_for_transaction_rules(
|
|||||||
# For deleted transactions, we might want to limit what actions can be performed
|
# For deleted transactions, we might want to limit what actions can be performed
|
||||||
if signal == "transaction_deleted":
|
if signal == "transaction_deleted":
|
||||||
# Process only create/update actions, not edit actions
|
# Process only create/update actions, not edit actions
|
||||||
for action in rule.update_or_create_transaction_actions.all():
|
for (
|
||||||
|
action
|
||||||
|
) in rule.update_or_create_transaction_actions.all():
|
||||||
try:
|
try:
|
||||||
_process_update_or_create_transaction_action(
|
_process_update_or_create_transaction_action(
|
||||||
action=action, simple_eval=simple
|
action=action, simple_eval=simple
|
||||||
@@ -99,31 +102,74 @@ def check_for_transaction_rules(
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Normal processing for non-deleted transactions
|
# Normal processing for non-deleted transactions
|
||||||
for action in rule.transaction_actions.all():
|
edit_actions = list(rule.transaction_actions.all())
|
||||||
try:
|
update_or_create_actions = list(
|
||||||
instance = _process_edit_transaction_action(
|
rule.update_or_create_transaction_actions.all()
|
||||||
instance=instance, action=action, simple_eval=simple
|
)
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(
|
|
||||||
f"Error processing edit transaction action {action.id}",
|
|
||||||
exc_info=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
simple.names.update(_get_names(instance))
|
# Check if any action has a non-zero order
|
||||||
if signal != "transaction_deleted":
|
has_custom_order = any(
|
||||||
instance.save()
|
a.order > 0 for a in edit_actions
|
||||||
|
) or any(a.order > 0 for a in update_or_create_actions)
|
||||||
|
|
||||||
for action in rule.update_or_create_transaction_actions.all():
|
if has_custom_order:
|
||||||
try:
|
# Combine and sort actions by order
|
||||||
_process_update_or_create_transaction_action(
|
all_actions = sorted(
|
||||||
action=action, simple_eval=simple
|
chain(edit_actions, update_or_create_actions),
|
||||||
)
|
key=lambda a: a.order,
|
||||||
except Exception as e:
|
)
|
||||||
logger.error(
|
|
||||||
f"Error processing update or create transaction action {action.id}",
|
for action in all_actions:
|
||||||
exc_info=True,
|
try:
|
||||||
)
|
if isinstance(action, TransactionRuleAction):
|
||||||
|
instance = _process_edit_transaction_action(
|
||||||
|
instance=instance,
|
||||||
|
action=action,
|
||||||
|
simple_eval=simple,
|
||||||
|
)
|
||||||
|
# Update names for next actions
|
||||||
|
simple.names.update(_get_names(instance))
|
||||||
|
else:
|
||||||
|
_process_update_or_create_transaction_action(
|
||||||
|
action=action, simple_eval=simple
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"Error processing action {action.id}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
# Save at the end
|
||||||
|
if signal != "transaction_deleted":
|
||||||
|
instance.save()
|
||||||
|
else:
|
||||||
|
# Original behavior
|
||||||
|
for action in edit_actions:
|
||||||
|
try:
|
||||||
|
instance = _process_edit_transaction_action(
|
||||||
|
instance=instance,
|
||||||
|
action=action,
|
||||||
|
simple_eval=simple,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"Error processing edit transaction action {action.id}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
simple.names.update(_get_names(instance))
|
||||||
|
if signal != "transaction_deleted":
|
||||||
|
instance.save()
|
||||||
|
|
||||||
|
for action in update_or_create_actions:
|
||||||
|
try:
|
||||||
|
_process_update_or_create_transaction_action(
|
||||||
|
action=action, simple_eval=simple
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"Error processing update or create transaction action {action.id}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
"Error while executing 'check_for_transaction_rules' task",
|
"Error while executing 'check_for_transaction_rules' task",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from itertools import chain
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
@@ -140,10 +142,18 @@ def transaction_rule_edit(request, transaction_rule_id):
|
|||||||
def transaction_rule_view(request, transaction_rule_id):
|
def transaction_rule_view(request, transaction_rule_id):
|
||||||
transaction_rule = get_object_or_404(TransactionRule, id=transaction_rule_id)
|
transaction_rule = get_object_or_404(TransactionRule, id=transaction_rule_id)
|
||||||
|
|
||||||
|
edit_actions = transaction_rule.transaction_actions.all()
|
||||||
|
update_or_create_actions = transaction_rule.update_or_create_transaction_actions.all()
|
||||||
|
|
||||||
|
all_actions = sorted(
|
||||||
|
chain(edit_actions, update_or_create_actions),
|
||||||
|
key=lambda a: a.order,
|
||||||
|
)
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"rules/fragments/transaction_rule/view.html",
|
"rules/fragments/transaction_rule/view.html",
|
||||||
{"transaction_rule": transaction_rule},
|
{"transaction_rule": transaction_rule, "all_actions": all_actions},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -175,6 +175,6 @@ class RecurringTransactionTests(TestCase):
|
|||||||
recurrence_type=RecurringTransaction.RecurrenceType.MONTH,
|
recurrence_type=RecurringTransaction.RecurrenceType.MONTH,
|
||||||
recurrence_interval=1,
|
recurrence_interval=1,
|
||||||
)
|
)
|
||||||
self.assertFalse(recurring.paused)
|
self.assertFalse(recurring.is_paused)
|
||||||
self.assertEqual(recurring.recurrence_interval, 1)
|
self.assertEqual(recurring.recurrence_interval, 1)
|
||||||
self.assertEqual(recurring.account.currency.code, "USD")
|
self.assertEqual(recurring.account.currency.code, "USD")
|
||||||
|
|||||||
@@ -30,75 +30,84 @@
|
|||||||
|
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<div class="tw:text-xl mb-2">{% translate 'Then...' %}</div>
|
<div class="tw:text-xl mb-2">{% translate 'Then...' %}</div>
|
||||||
{% for action in transaction_rule.transaction_actions.all %}
|
{% for action in all_actions %}
|
||||||
<div class="card mb-3">
|
{% if action.action_type == "edit_transaction" %}
|
||||||
<div class="card-header">
|
<div class="card mb-3">
|
||||||
<div><span class="badge text-bg-primary">{% trans 'Edit transaction' %}</span></div>
|
<div class="card-header">
|
||||||
</div>
|
<div>
|
||||||
<div class="card-body">
|
{% if action.order != 0 %}<span class="badge text-bg-secondary">{{ action.order }}</span>{% endif %}
|
||||||
<div>{% translate 'Set' %} <span
|
<span class="badge text-bg-primary">{% trans 'Edit transaction' %}</span>
|
||||||
class="badge text-bg-secondary">{{ action.get_field_display }}</span> {% translate 'to' %}</div>
|
</div>
|
||||||
<div class="text-bg-secondary rounded-3 mt-3 p-2">{{ action.value }}</div>
|
</div>
|
||||||
</div>
|
<div class="card-body">
|
||||||
<div class="card-footer text-end">
|
<div>
|
||||||
<a class="text-decoration-none tw:text-gray-400 p-1"
|
{% translate 'Set' %} <span
|
||||||
role="button"
|
class="badge text-bg-secondary">{{ action.get_field_display }}</span> {% translate 'to' %}
|
||||||
data-bs-toggle="tooltip"
|
</div>
|
||||||
data-bs-title="{% translate "Edit" %}"
|
<div class="text-bg-secondary rounded-3 mt-3 p-2">{{ action.value }}</div>
|
||||||
hx-get="{% url 'transaction_rule_action_edit' transaction_rule_action_id=action.id %}"
|
</div>
|
||||||
hx-target="#generic-offcanvas">
|
<div class="card-footer text-end">
|
||||||
<i class="fa-solid fa-pencil fa-fw"></i>
|
<a class="text-decoration-none tw:text-gray-400 p-1"
|
||||||
</a>
|
role="button"
|
||||||
<a class="text-danger text-decoration-none p-1"
|
data-bs-toggle="tooltip"
|
||||||
role="button"
|
data-bs-title="{% translate 'Edit' %}"
|
||||||
data-bs-toggle="tooltip"
|
hx-get="{% url 'transaction_rule_action_edit' transaction_rule_action_id=action.id %}"
|
||||||
data-bs-title="{% translate "Delete" %}"
|
hx-target="#generic-offcanvas">
|
||||||
hx-delete="{% url 'transaction_rule_action_delete' transaction_rule_action_id=action.id %}"
|
<i class="fa-solid fa-pencil fa-fw"></i>
|
||||||
hx-trigger='confirmed'
|
</a>
|
||||||
data-bypass-on-ctrl="true"
|
<a class="text-danger text-decoration-none p-1"
|
||||||
data-title="{% translate "Are you sure?" %}"
|
role="button"
|
||||||
data-text="{% translate "You won't be able to revert this!" %}"
|
data-bs-toggle="tooltip"
|
||||||
data-confirm-text="{% translate "Yes, delete it!" %}"
|
data-bs-title="{% translate 'Delete' %}"
|
||||||
_="install prompt_swal">
|
hx-delete="{% url 'transaction_rule_action_delete' transaction_rule_action_id=action.id %}"
|
||||||
|
hx-trigger='confirmed'
|
||||||
|
data-bypass-on-ctrl="true"
|
||||||
|
data-title="{% translate 'Are you sure?' %}"
|
||||||
|
data-text="{% translate "You won't be able to revert this!" %}"
|
||||||
|
data-confirm-text="{% translate 'Yes, delete it!' %}"
|
||||||
|
_="install prompt_swal">
|
||||||
<i class="fa-solid fa-trash fa-fw"></i>
|
<i class="fa-solid fa-trash fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% elif action.action_type == "update_or_create_transaction" %}
|
||||||
{% endfor %}
|
<div class="card mb-3">
|
||||||
{% for action in transaction_rule.update_or_create_transaction_actions.all %}
|
<div class="card-header">
|
||||||
<div class="card mb-3">
|
<div>
|
||||||
<div class="card-header">
|
{% if action.order != 0 %}<span class="badge text-bg-secondary">{{ action.order }}</span>{% endif %}
|
||||||
<div><span class="badge text-bg-primary">{% trans 'Update or create transaction' %}</span></div>
|
<span class="badge text-bg-primary">{% trans 'Update or create transaction' %}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
</div>
|
||||||
<div>{% trans 'Edit to view' %}</div>
|
<div class="card-body">
|
||||||
</div>
|
<div>{% trans 'Edit to view' %}</div>
|
||||||
<div class="card-footer text-end">
|
</div>
|
||||||
<a class="text-decoration-none tw:text-gray-400 p-1"
|
<div class="card-footer text-end">
|
||||||
role="button"
|
<a class="text-decoration-none tw:text-gray-400 p-1"
|
||||||
data-bs-toggle="tooltip"
|
role="button"
|
||||||
data-bs-title="{% translate "Edit" %}"
|
data-bs-toggle="tooltip"
|
||||||
hx-get="{% url 'update_or_create_transaction_rule_action_edit' pk=action.id %}"
|
data-bs-title="{% translate 'Edit' %}"
|
||||||
hx-target="#generic-offcanvas">
|
hx-get="{% url 'update_or_create_transaction_rule_action_edit' pk=action.id %}"
|
||||||
<i class="fa-solid fa-pencil fa-fw"></i>
|
hx-target="#generic-offcanvas">
|
||||||
</a>
|
<i class="fa-solid fa-pencil fa-fw"></i>
|
||||||
<a class="text-danger text-decoration-none p-1"
|
</a>
|
||||||
role="button"
|
<a class="text-danger text-decoration-none p-1"
|
||||||
data-bs-toggle="tooltip"
|
role="button"
|
||||||
data-bs-title="{% translate "Delete" %}"
|
data-bs-toggle="tooltip"
|
||||||
hx-delete="{% url 'update_or_create_transaction_rule_action_delete' pk=action.id %}"
|
data-bs-title="{% translate 'Delete' %}"
|
||||||
hx-trigger='confirmed'
|
hx-delete="{% url 'update_or_create_transaction_rule_action_delete' pk=action.id %}"
|
||||||
data-bypass-on-ctrl="true"
|
hx-trigger='confirmed'
|
||||||
data-title="{% translate "Are you sure?" %}"
|
data-bypass-on-ctrl="true"
|
||||||
data-text="{% translate "You won't be able to revert this!" %}"
|
data-title="{% translate 'Are you sure?' %}"
|
||||||
data-confirm-text="{% translate "Yes, delete it!" %}"
|
data-text="{% translate "You won't be able to revert this!" %}"
|
||||||
_="install prompt_swal">
|
data-confirm-text="{% translate 'Yes, delete it!' %}"
|
||||||
|
_="install prompt_swal">
|
||||||
<i class="fa-solid fa-trash fa-fw"></i>
|
<i class="fa-solid fa-trash fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if not transaction_rule.update_or_create_transaction_actions.all and not transaction_rule.transaction_actions.all %}
|
{% if not all_actions %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% translate 'This rule has no actions' %}
|
{% translate 'This rule has no actions' %}
|
||||||
|
|||||||
Reference in New Issue
Block a user