mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-03-18 07:24:00 +01:00
425 lines
14 KiB
Python
425 lines
14 KiB
Python
from django.db import models
|
|
from django.db.models import Q
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
|
class TransactionRule(models.Model):
|
|
active = models.BooleanField(default=True)
|
|
on_update = models.BooleanField(default=False)
|
|
on_create = models.BooleanField(default=True)
|
|
name = models.CharField(max_length=100, verbose_name=_("Name"))
|
|
description = models.TextField(blank=True, null=True, verbose_name=_("Description"))
|
|
trigger = models.TextField(verbose_name=_("Trigger"))
|
|
|
|
class Meta:
|
|
verbose_name = _("Transaction rule")
|
|
verbose_name_plural = _("Transaction rules")
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class TransactionRuleAction(models.Model):
|
|
class Field(models.TextChoices):
|
|
account = "account", _("Account")
|
|
type = "type", _("Type")
|
|
is_paid = "is_paid", _("Paid")
|
|
date = "date", _("Date")
|
|
reference_date = "reference_date", _("Reference Date")
|
|
amount = "amount", _("Amount")
|
|
description = "description", _("Description")
|
|
notes = "notes", _("Notes")
|
|
category = "category", _("Category")
|
|
tags = "tags", _("Tags")
|
|
entities = "entities", _("Entities")
|
|
|
|
rule = models.ForeignKey(
|
|
TransactionRule,
|
|
on_delete=models.CASCADE,
|
|
related_name="transaction_actions",
|
|
verbose_name=_("Rule"),
|
|
)
|
|
field = models.CharField(
|
|
max_length=50,
|
|
choices=Field,
|
|
verbose_name=_("Field"),
|
|
)
|
|
value = models.TextField(verbose_name=_("Value"))
|
|
|
|
def __str__(self):
|
|
return f"{self.rule} - {self.field} - {self.value}"
|
|
|
|
class Meta:
|
|
verbose_name = _("Edit transaction action")
|
|
verbose_name_plural = _("Edit transaction actions")
|
|
unique_together = (("rule", "field"),)
|
|
|
|
|
|
class UpdateOrCreateTransactionRuleAction(models.Model):
|
|
"""
|
|
Will attempt to find and update latest matching transaction, or create new if none found.
|
|
"""
|
|
|
|
class SearchOperator(models.TextChoices):
|
|
EXACT = "exact", _("is exactly")
|
|
CONTAINS = "contains", _("contains")
|
|
STARTSWITH = "startswith", _("starts with")
|
|
ENDSWITH = "endswith", _("ends with")
|
|
EQ = "eq", _("equals")
|
|
GT = "gt", _("greater than")
|
|
LT = "lt", _("less than")
|
|
GTE = "gte", _("greater than or equal")
|
|
LTE = "lte", _("less than or equal")
|
|
|
|
rule = models.ForeignKey(
|
|
TransactionRule,
|
|
on_delete=models.CASCADE,
|
|
related_name="update_or_create_transaction_actions",
|
|
verbose_name=_("Rule"),
|
|
)
|
|
|
|
filter = models.TextField(
|
|
verbose_name=_("Filter"),
|
|
blank=True,
|
|
help_text=_(
|
|
"Generic expression to enable or disable execution. Should evaluate to True or False"
|
|
),
|
|
)
|
|
|
|
# Search fields with operators
|
|
search_account = models.TextField(
|
|
verbose_name=_("Search Account"),
|
|
blank=True,
|
|
help_text=_("Expression to match transaction account (ID or name)"),
|
|
)
|
|
search_account_operator = models.CharField(
|
|
max_length=10,
|
|
choices=SearchOperator.choices,
|
|
default=SearchOperator.EXACT,
|
|
verbose_name=_("Account Operator"),
|
|
)
|
|
|
|
search_type = models.TextField(
|
|
verbose_name=_("Search Type"),
|
|
blank=True,
|
|
help_text=_("Expression to match transaction type ('IN' or 'EX')"),
|
|
)
|
|
search_type_operator = models.CharField(
|
|
max_length=10,
|
|
choices=SearchOperator.choices,
|
|
default=SearchOperator.EXACT,
|
|
verbose_name=_("Type Operator"),
|
|
)
|
|
|
|
search_is_paid = models.TextField(
|
|
verbose_name=_("Search Is Paid"),
|
|
blank=True,
|
|
help_text=_("Expression to match transaction paid status"),
|
|
)
|
|
search_is_paid_operator = models.CharField(
|
|
max_length=10,
|
|
choices=SearchOperator.choices,
|
|
default=SearchOperator.EXACT,
|
|
verbose_name=_("Is Paid Operator"),
|
|
)
|
|
|
|
search_date = models.TextField(
|
|
verbose_name=_("Search Date"),
|
|
blank=True,
|
|
help_text=_("Expression to match transaction date"),
|
|
)
|
|
search_date_operator = models.CharField(
|
|
max_length=10,
|
|
choices=SearchOperator.choices,
|
|
default=SearchOperator.EXACT,
|
|
verbose_name=_("Date Operator"),
|
|
)
|
|
|
|
search_reference_date = models.TextField(
|
|
verbose_name=_("Search Reference Date"),
|
|
blank=True,
|
|
help_text=_("Expression to match transaction reference date"),
|
|
)
|
|
search_reference_date_operator = models.CharField(
|
|
max_length=10,
|
|
choices=SearchOperator.choices,
|
|
default=SearchOperator.EXACT,
|
|
verbose_name=_("Reference Date Operator"),
|
|
)
|
|
|
|
search_amount = models.TextField(
|
|
verbose_name=_("Search Amount"),
|
|
blank=True,
|
|
help_text=_("Expression to match transaction amount"),
|
|
)
|
|
search_amount_operator = models.CharField(
|
|
max_length=10,
|
|
choices=SearchOperator.choices,
|
|
default=SearchOperator.EXACT,
|
|
verbose_name=_("Amount Operator"),
|
|
)
|
|
|
|
search_description = models.TextField(
|
|
verbose_name=_("Search Description"),
|
|
blank=True,
|
|
help_text=_("Expression to match transaction description"),
|
|
)
|
|
search_description_operator = models.CharField(
|
|
max_length=10,
|
|
choices=SearchOperator.choices,
|
|
default=SearchOperator.CONTAINS,
|
|
verbose_name=_("Description Operator"),
|
|
)
|
|
|
|
search_notes = models.TextField(
|
|
verbose_name=_("Search Notes"),
|
|
blank=True,
|
|
help_text=_("Expression to match transaction notes"),
|
|
)
|
|
search_notes_operator = models.CharField(
|
|
max_length=10,
|
|
choices=SearchOperator.choices,
|
|
default=SearchOperator.CONTAINS,
|
|
verbose_name=_("Notes Operator"),
|
|
)
|
|
|
|
search_category = models.TextField(
|
|
verbose_name=_("Search Category"),
|
|
blank=True,
|
|
help_text=_("Expression to match transaction category (ID or name)"),
|
|
)
|
|
search_category_operator = models.CharField(
|
|
max_length=10,
|
|
choices=SearchOperator.choices,
|
|
default=SearchOperator.EXACT,
|
|
verbose_name=_("Category Operator"),
|
|
)
|
|
|
|
search_tags = models.TextField(
|
|
verbose_name=_("Search Tags"),
|
|
blank=True,
|
|
help_text=_("Expression to match transaction tags (list of IDs or names)"),
|
|
)
|
|
search_tags_operator = models.CharField(
|
|
max_length=10,
|
|
choices=SearchOperator.choices,
|
|
default=SearchOperator.CONTAINS,
|
|
verbose_name=_("Tags Operator"),
|
|
)
|
|
|
|
search_entities = models.TextField(
|
|
verbose_name=_("Search Entities"),
|
|
blank=True,
|
|
help_text=_("Expression to match transaction entities (list of IDs or names)"),
|
|
)
|
|
search_entities_operator = models.CharField(
|
|
max_length=10,
|
|
choices=SearchOperator.choices,
|
|
default=SearchOperator.CONTAINS,
|
|
verbose_name=_("Entities Operator"),
|
|
)
|
|
|
|
search_internal_note = models.TextField(
|
|
verbose_name=_("Search Internal Note"),
|
|
blank=True,
|
|
help_text=_("Expression to match transaction internal note"),
|
|
)
|
|
search_internal_note_operator = models.CharField(
|
|
max_length=10,
|
|
choices=SearchOperator.choices,
|
|
default=SearchOperator.EXACT,
|
|
verbose_name=_("Internal Note Operator"),
|
|
)
|
|
|
|
search_internal_id = models.TextField(
|
|
verbose_name=_("Search Internal ID"),
|
|
blank=True,
|
|
help_text=_("Expression to match transaction internal ID"),
|
|
)
|
|
search_internal_id_operator = models.CharField(
|
|
max_length=10,
|
|
choices=SearchOperator.choices,
|
|
default=SearchOperator.EXACT,
|
|
verbose_name=_("Internal ID Operator"),
|
|
)
|
|
|
|
# Set fields
|
|
set_account = models.TextField(
|
|
verbose_name=_("Set Account"),
|
|
blank=True,
|
|
help_text=_("Expression for account to set (ID or name)"),
|
|
)
|
|
set_type = models.TextField(
|
|
verbose_name=_("Set Type"),
|
|
blank=True,
|
|
help_text=_("Expression for type to set ('IN' or 'EX')"),
|
|
)
|
|
set_is_paid = models.TextField(
|
|
verbose_name=_("Set Is Paid"),
|
|
blank=True,
|
|
help_text=_("Expression for paid status to set"),
|
|
)
|
|
set_date = models.TextField(
|
|
verbose_name=_("Set Date"),
|
|
blank=True,
|
|
help_text=_("Expression for date to set"),
|
|
)
|
|
set_reference_date = models.TextField(
|
|
verbose_name=_("Set Reference Date"),
|
|
blank=True,
|
|
help_text=_("Expression for reference date to set"),
|
|
)
|
|
set_amount = models.TextField(
|
|
verbose_name=_("Set Amount"),
|
|
blank=True,
|
|
help_text=_("Expression for amount to set"),
|
|
)
|
|
set_description = models.TextField(
|
|
verbose_name=_("Set Description"),
|
|
blank=True,
|
|
help_text=_("Expression for description to set"),
|
|
)
|
|
set_notes = models.TextField(
|
|
verbose_name=_("Set Notes"),
|
|
blank=True,
|
|
help_text=_("Expression for notes to set"),
|
|
)
|
|
set_internal_note = models.TextField(
|
|
verbose_name=_("Set Internal Note"),
|
|
blank=True,
|
|
help_text=_("Expression for internal note to set"),
|
|
)
|
|
set_internal_id = models.TextField(
|
|
verbose_name=_("Set Internal ID"),
|
|
blank=True,
|
|
help_text=_("Expression for internal ID to set"),
|
|
)
|
|
set_entities = models.TextField(
|
|
verbose_name=_("Set Entities"),
|
|
blank=True,
|
|
help_text=_("Expression for entities to set (list of IDs or names)"),
|
|
)
|
|
set_category = models.TextField(
|
|
verbose_name=_("Set Category"),
|
|
blank=True,
|
|
help_text=_("Expression for category to set (ID or name)"),
|
|
)
|
|
set_tags = models.TextField(
|
|
verbose_name=_("Set Tags"),
|
|
blank=True,
|
|
help_text=_("Expression for tags to set (list of IDs or names)"),
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = _("Update or create transaction action")
|
|
verbose_name_plural = _("Update or create transaction actions")
|
|
|
|
def __str__(self):
|
|
return f"Update or create transaction action for {self.rule}"
|
|
|
|
def build_search_query(self, simple):
|
|
"""Builds Q objects based on search fields and their operators"""
|
|
search_query = Q()
|
|
|
|
def add_to_query(field_name, value, operator):
|
|
if isinstance(value, (int, str)):
|
|
lookup = f"{field_name}__{operator}"
|
|
return Q(**{lookup: value})
|
|
return Q()
|
|
|
|
if self.search_account:
|
|
value = simple.eval(self.search_account)
|
|
if isinstance(value, int):
|
|
search_query &= add_to_query(
|
|
"account_id", value, self.search_account_operator
|
|
)
|
|
else:
|
|
search_query &= add_to_query(
|
|
"account__name", value, self.search_account_operator
|
|
)
|
|
|
|
if self.search_type:
|
|
value = simple.eval(self.search_type)
|
|
search_query &= add_to_query("type", value, self.search_type_operator)
|
|
|
|
if self.search_is_paid:
|
|
value = simple.eval(self.search_is_paid)
|
|
search_query &= add_to_query("is_paid", value, self.search_is_paid_operator)
|
|
|
|
if self.search_date:
|
|
value = simple.eval(self.search_date)
|
|
search_query &= add_to_query("date", value, self.search_date_operator)
|
|
|
|
if self.search_reference_date:
|
|
value = simple.eval(self.search_reference_date)
|
|
search_query &= add_to_query(
|
|
"reference_date", value, self.search_reference_date_operator
|
|
)
|
|
|
|
if self.search_amount:
|
|
value = simple.eval(self.search_amount)
|
|
search_query &= add_to_query("amount", value, self.search_amount_operator)
|
|
|
|
if self.search_description:
|
|
value = simple.eval(self.search_description)
|
|
search_query &= add_to_query(
|
|
"description", value, self.search_description_operator
|
|
)
|
|
|
|
if self.search_notes:
|
|
value = simple.eval(self.search_notes)
|
|
search_query &= add_to_query("notes", value, self.search_notes_operator)
|
|
|
|
if self.search_internal_note:
|
|
value = simple.eval(self.search_internal_note)
|
|
search_query &= add_to_query(
|
|
"internal_note", value, self.search_internal_note_operator
|
|
)
|
|
|
|
if self.search_internal_id:
|
|
value = simple.eval(self.search_internal_id)
|
|
search_query &= add_to_query(
|
|
"internal_id", value, self.search_internal_id_operator
|
|
)
|
|
|
|
if self.search_category:
|
|
value = simple.eval(self.search_category)
|
|
if isinstance(value, int):
|
|
search_query &= add_to_query(
|
|
"category_id", value, self.search_category_operator
|
|
)
|
|
else:
|
|
search_query &= add_to_query(
|
|
"category__name", value, self.search_category_operator
|
|
)
|
|
|
|
if self.search_tags:
|
|
tags_value = simple.eval(self.search_tags)
|
|
if isinstance(tags_value, (list, tuple)):
|
|
for tag in tags_value:
|
|
if isinstance(tag, int):
|
|
search_query &= Q(tags__id=tag)
|
|
else:
|
|
search_query &= Q(tags__name__iexact=tag)
|
|
elif isinstance(tags_value, (int, str)):
|
|
if isinstance(tags_value, int):
|
|
search_query &= Q(tags__id=tags_value)
|
|
else:
|
|
search_query &= Q(tags__name__iexact=tags_value)
|
|
|
|
if self.search_entities:
|
|
entities_value = simple.eval(self.search_entities)
|
|
if isinstance(entities_value, (list, tuple)):
|
|
for entity in entities_value:
|
|
if isinstance(entity, int):
|
|
search_query &= Q(entities__id=entity)
|
|
else:
|
|
search_query &= Q(entities__name__iexact=entity)
|
|
elif isinstance(entities_value, (int, str)):
|
|
if isinstance(entities_value, int):
|
|
search_query &= Q(entities__id=entities_value)
|
|
else:
|
|
search_query &= Q(entities__name__iexact=entities_value)
|
|
|
|
return search_query
|