Add invoice model with metadata and tags; update search and templates for invoice handling
Progress #44
This commit is contained in:
parent
608da4be55
commit
c153000d3d
8 changed files with 87 additions and 9 deletions
|
@ -7,3 +7,12 @@ def get_icons():
|
||||||
data = json.loads(request.urlopen(url).read())
|
data = json.loads(request.urlopen(url).read())
|
||||||
|
|
||||||
return [i.removesuffix("-line") for i in data.keys() if i.endswith("-line")]
|
return [i.removesuffix("-line") for i in data.keys() if i.endswith("-line")]
|
||||||
|
|
||||||
|
|
||||||
|
def pdf_outline_to_str(outline):
|
||||||
|
return " ".join(
|
||||||
|
(
|
||||||
|
dest.title if not isinstance(dest, list) else pdf_outline_to_str(dest)
|
||||||
|
for dest in outline
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
@ -41,7 +41,13 @@
|
||||||
{% transaction_table transactions n_max=8 transactions_url=t_url %}
|
{% transaction_table transactions n_max=8 transactions_url=t_url %}
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not accounts and not categories and not transactions %}
|
{% if invoices %}
|
||||||
|
<section>
|
||||||
|
<h3>{% translate "Invoices" %}</h3>
|
||||||
|
{% invoice_table invoices=invoices %}
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
{% if not accounts and not categories and not transactions and not invoices %}
|
||||||
<p>{% translate "No results found." %}</p>
|
<p>{% translate "No results found." %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock body %}
|
{% endblock body %}
|
||||||
|
|
|
@ -26,5 +26,6 @@ class SearchView(LoginRequiredMixin, TemplateView):
|
||||||
context["transactions"] = _user.transaction_set.search(self.kwargs["search"])
|
context["transactions"] = _user.transaction_set.search(self.kwargs["search"])
|
||||||
context["accounts"] = _user.account_set.search(self.kwargs["search"])
|
context["accounts"] = _user.account_set.search(self.kwargs["search"])
|
||||||
context["categories"] = _user.category_set.search(self.kwargs["search"])
|
context["categories"] = _user.category_set.search(self.kwargs["search"])
|
||||||
|
context["invoices"] = _user.invoice_set.search(self.kwargs["search"])[:10]
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 4.2.7 on 2025-01-05 14:51
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("transaction", "0004_remove_transaction_account"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="invoice",
|
||||||
|
name="metadata",
|
||||||
|
field=models.TextField(blank=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="invoice",
|
||||||
|
name="tags",
|
||||||
|
field=models.TextField(blank=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -8,7 +8,9 @@ from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from main.models import NummiModel, NummiQuerySet
|
from main.models import NummiModel, NummiQuerySet
|
||||||
|
from main.utils import pdf_outline_to_str
|
||||||
from media.utils import get_path
|
from media.utils import get_path
|
||||||
|
from pypdf import PdfReader
|
||||||
from statement.models import Statement
|
from statement.models import Statement
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,6 +76,13 @@ class Transaction(NummiModel):
|
||||||
verbose_name_plural = _("Transactions")
|
verbose_name_plural = _("Transactions")
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceQuerySet(NummiQuerySet):
|
||||||
|
fields = {
|
||||||
|
"metadata": "B",
|
||||||
|
"tags": "C",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Invoice(NummiModel):
|
class Invoice(NummiModel):
|
||||||
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
|
@ -88,12 +97,35 @@ class Invoice(NummiModel):
|
||||||
transaction = models.ForeignKey(
|
transaction = models.ForeignKey(
|
||||||
Transaction, on_delete=models.CASCADE, editable=False
|
Transaction, on_delete=models.CASCADE, editable=False
|
||||||
)
|
)
|
||||||
|
metadata = models.TextField(blank=True)
|
||||||
|
tags = models.TextField(blank=True)
|
||||||
|
|
||||||
|
objects = InvoiceQuerySet.as_manager()
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
if Invoice.objects.filter(id=self.id).exists():
|
if Invoice.objects.filter(id=self.id).exists():
|
||||||
_prever = Invoice.objects.get(id=self.id)
|
_prever = Invoice.objects.get(id=self.id)
|
||||||
if _prever.file and _prever.file != self.file:
|
if _prever.file and _prever.file != self.file:
|
||||||
Path(_prever.file.path).unlink(missing_ok=True)
|
Path(_prever.file.path).unlink(missing_ok=True)
|
||||||
|
|
||||||
|
reader = PdfReader(self.file)
|
||||||
|
|
||||||
|
self.metadata = " ".join(
|
||||||
|
(
|
||||||
|
m
|
||||||
|
for m in (
|
||||||
|
reader.metadata.title,
|
||||||
|
reader.metadata.author,
|
||||||
|
reader.metadata.subject,
|
||||||
|
)
|
||||||
|
if m
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
_tags = pdf_outline_to_str(reader.outline)
|
||||||
|
_tags += " ".join((page.extract_text() for page in reader.pages))
|
||||||
|
self.tags = " ".join((tag for tag in _tags.split() if len(tag) >= 3))
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -3,14 +3,19 @@
|
||||||
<ul class="invoices">
|
<ul class="invoices">
|
||||||
{% for invoice in invoices %}
|
{% for invoice in invoices %}
|
||||||
<li>
|
<li>
|
||||||
|
{% if not transaction %}<span>{{ invoice.transaction.name }}</span>{% endif %}
|
||||||
<a class="title" href="{{ invoice.file.url }}">{{ "file"|remix }}{{ invoice.name }} [{{ invoice.file|extension }}]</a>
|
<a class="title" href="{{ invoice.file.url }}">{{ "file"|remix }}{{ invoice.name }} [{{ invoice.file|extension }}]</a>
|
||||||
<a href="{{ invoice.get_absolute_url }}">{{ "file-edit"|remix }}{% translate "Edit" %}</a>
|
{% if transaction %}
|
||||||
|
<a href="{{ invoice.get_absolute_url }}">{{ "file-edit"|remix }}{% translate "Edit" %}</a>
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<li class="new">
|
{% if transaction %}
|
||||||
<span>
|
<li class="new">
|
||||||
<a href="{% url "new_invoice" transaction.pk %}">{{ "file-add"|remix }}{% translate "New invoice" %}</a>
|
<span>
|
||||||
</span>
|
<a href="{% url "new_invoice" transaction.pk %}">{{ "file-add"|remix }}{% translate "New invoice" %}</a>
|
||||||
</li>
|
</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -36,10 +36,12 @@ def transaction_filters(context, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
@register.inclusion_tag("transaction/invoice_table.html")
|
@register.inclusion_tag("transaction/invoice_table.html")
|
||||||
def invoice_table(transaction, **kwargs):
|
def invoice_table(transaction=None, **kwargs):
|
||||||
|
if transaction:
|
||||||
|
kwargs.setdefault("invoices", transaction.invoice_set.all())
|
||||||
|
|
||||||
return kwargs | {
|
return kwargs | {
|
||||||
"transaction": transaction,
|
"transaction": transaction,
|
||||||
"invoices": transaction.invoice_set.all(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ depends=(
|
||||||
"python-toml"
|
"python-toml"
|
||||||
"python-psycopg"
|
"python-psycopg"
|
||||||
"python-dateutil"
|
"python-dateutil"
|
||||||
|
"python-pypdf"
|
||||||
)
|
)
|
||||||
makedepends=("git")
|
makedepends=("git")
|
||||||
optdepends=("postgresql: database")
|
optdepends=("postgresql: database")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue