mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-11-13 12:23:44 +01:00
An import run can now be given an account, and every transaction it creates goes into that account instead of the one the profile maps. This lets a single profile serve several accounts at the same bank, for statements that only name the account in a header cell or not at all, instead of keeping one copy of the profile per account. - ImportRun gets an optional `account` (SET_NULL), shown on the run card. - The upload form offers the user's active accounts when the profile imports transactions, and leaves the field out for profiles that import accounts, currencies, categories, tags or entities. - CSV and Excel rows take the run's account before deduplication, so rules comparing `account` check against the picked account. The profile's account mapping is skipped for these rows. - QIF files use the run's account instead of the account named after the file. - The import API accepts an optional `account_id`, limited to accounts the user can access, rejected for profiles that don't import transactions, and returns `account` on import runs. Leaving the account empty keeps the current behaviour. Closes #538
121 lines
3.7 KiB
Python
121 lines
3.7 KiB
Python
import yaml
|
|
|
|
from django.core.exceptions import ValidationError
|
|
from django.db import models
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from apps.common.models import (
|
|
OwnedObject,
|
|
OwnedObjectManager,
|
|
SharedObject,
|
|
SharedObjectManager,
|
|
)
|
|
from apps.import_app.schemas import version_1
|
|
|
|
|
|
class ImportProfile(SharedObject):
|
|
class Versions(models.IntegerChoices):
|
|
VERSION_1 = 1, "Version 1"
|
|
|
|
name = models.CharField(max_length=100, verbose_name=_("Name"))
|
|
yaml_config = models.TextField(verbose_name=_("YAML Configuration"))
|
|
version = models.IntegerField(
|
|
choices=Versions,
|
|
default=Versions.VERSION_1,
|
|
verbose_name=_("Version"),
|
|
)
|
|
|
|
objects = SharedObjectManager()
|
|
all_objects = models.Manager() # Unfiltered manager
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class Meta:
|
|
ordering = ["name"]
|
|
|
|
def get_version_display(self):
|
|
version_number = self.Versions(self.version).name.split("_")[1]
|
|
return _("Version {number}").format(number=version_number)
|
|
|
|
@property
|
|
def imports_transactions(self):
|
|
"""Whether this profile creates transactions, as opposed to accounts, tags, etc."""
|
|
try:
|
|
config = version_1.ImportProfileSchema(**yaml.safe_load(self.yaml_config))
|
|
except Exception:
|
|
return False
|
|
return config.settings.importing == "transactions"
|
|
|
|
def clean(self):
|
|
if self.version and self.version == self.Versions.VERSION_1:
|
|
try:
|
|
yaml_data = yaml.safe_load(self.yaml_config)
|
|
version_1.ImportProfileSchema(**yaml_data)
|
|
except Exception as e:
|
|
raise ValidationError(
|
|
{"yaml_config": _("Invalid YAML Configuration: ") + str(e)}
|
|
)
|
|
|
|
|
|
class ImportRun(OwnedObject):
|
|
class Status(models.TextChoices):
|
|
QUEUED = "QUEUED", _("Queued")
|
|
PROCESSING = "PROCESSING", _("Processing")
|
|
FAILED = "FAILED", _("Failed")
|
|
FINISHED = "FINISHED", _("Finished")
|
|
|
|
status = models.CharField(
|
|
max_length=10,
|
|
choices=Status,
|
|
default=Status.QUEUED,
|
|
verbose_name=_("Status"),
|
|
)
|
|
profile = models.ForeignKey(
|
|
ImportProfile,
|
|
on_delete=models.CASCADE,
|
|
)
|
|
file_name = models.CharField(
|
|
max_length=10000,
|
|
help_text=_("File name"),
|
|
)
|
|
account = models.ForeignKey(
|
|
"accounts.Account",
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name="import_runs",
|
|
verbose_name=_("Account"),
|
|
help_text=_(
|
|
"Import every transaction into this account. Leave empty to let the "
|
|
"profile pick the account."
|
|
),
|
|
)
|
|
transactions = models.ManyToManyField(
|
|
"transactions.Transaction", related_name="import_runs"
|
|
)
|
|
tags = models.ManyToManyField(
|
|
"transactions.TransactionTag", related_name="import_runs"
|
|
)
|
|
categories = models.ManyToManyField(
|
|
"transactions.TransactionCategory", related_name="import_runs"
|
|
)
|
|
entities = models.ManyToManyField(
|
|
"transactions.TransactionEntity", related_name="import_runs"
|
|
)
|
|
currencies = models.ManyToManyField(
|
|
"currencies.Currency", related_name="import_runs"
|
|
)
|
|
|
|
logs = models.TextField(blank=True)
|
|
processed_rows = models.IntegerField(default=0)
|
|
total_rows = models.IntegerField(default=0)
|
|
successful_rows = models.IntegerField(default=0)
|
|
skipped_rows = models.IntegerField(default=0)
|
|
failed_rows = models.IntegerField(default=0)
|
|
started_at = models.DateTimeField(null=True)
|
|
finished_at = models.DateTimeField(null=True)
|
|
|
|
objects = OwnedObjectManager()
|
|
all_objects = models.Manager() # Unfiltered manager
|