Compare commits

..

33 Commits
0.1.0 ... 0.1.8

Author SHA1 Message Date
Herculino Trotta
8c5a9efe05 Merge pull request #19 from eitchtee/dev
locale(pt-BR): update translation
2025-01-04 18:24:47 -03:00
Herculino Trotta
f940414b5c locale(pt-BR): update translation 2025-01-04 18:23:01 -03:00
Herculino Trotta
2d8e97a27e Merge pull request #18
feat: allow for deactivating Tags, Categories and Entities, hiding them from menus
2025-01-04 18:17:42 -03:00
Herculino Trotta
5ccb9ff152 locale: add lazy translations to missing ValidationErrors 2025-01-04 18:17:06 -03:00
Herculino Trotta
3c0a2d82ac feat: allow for deactivating Tags, Categories and Entities, hiding them from menus 2025-01-04 18:13:11 -03:00
Herculino Trotta
62f049cbb2 Merge pull request #17
feat(fields:forms:dynamic-select): support existing objects not currently on the queryset
2025-01-04 18:00:33 -03:00
Herculino Trotta
7a759be357 feat(fields:forms:dynamic-select): support existing objects not currently on the queryset
and add create_field to DynamicModelChoiceField
2025-01-04 17:59:59 -03:00
Herculino Trotta
6297e73307 Merge pull request #16
feat(transactions): properly sum income and expense when selected
2025-01-04 01:33:05 -03:00
Herculino Trotta
eb753bb30e feat(transactions): properly sum income and expense when selected
also added a flatTotal (old behavior) for future use
2025-01-04 01:32:09 -03:00
Herculino Trotta
1047fb23dd fix(networth): charts not changing between views 2025-01-03 17:50:41 -03:00
Herculino Trotta
c861b9ae07 fix(networth): charts not changing between views 2025-01-03 17:36:10 -03:00
Herculino Trotta
4be849f5de github(release): add gha cache 2024-12-28 02:42:43 -03:00
Herculino Trotta
3e73332a93 locale(pt-BR): update translation 2024-12-28 02:32:43 -03:00
Herculino Trotta
ae2217e760 feat(tools:currency-converter): add a button to invert the selected currencies 2024-12-28 00:56:15 -03:00
Herculino Trotta
e2bf699be0 feat(tools:currency-converter): make it responsive 2024-12-28 00:56:15 -03:00
Herculino Trotta
e760d42c2d github(release): drop ghcr.io in favor of DockerHub 2024-12-27 12:53:43 -03:00
Herculino Trotta
d541b30280 docs: registry changes (#12)
* docker(prod): update docker-compose.prod.yml to use registry image

* docs(README): update install instructions to use registry image
2024-12-27 12:25:12 -03:00
dependabot[bot]
366c0b475d build(deps): bump nanoid from 3.3.7 to 3.3.8 in /frontend (#8)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-27 12:17:41 -03:00
dependabot[bot]
8576b74aff build(deps): bump http-proxy-middleware from 2.0.6 to 2.0.7 in /frontend (#9)
Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.6 to 2.0.7.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.7/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.6...v2.0.7)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-27 12:17:34 -03:00
dependabot[bot]
d4b11bd350 build(deps): bump path-to-regexp and express in /frontend (#10)
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `path-to-regexp` from 0.1.10 to 0.1.12
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.10...v0.1.12)

Updates `express` from 4.21.0 to 4.21.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.0...4.21.2)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-27 12:17:28 -03:00
dependabot[bot]
c8c34c2c56 build(deps): bump cookie and express in /frontend (#11)
Bumps [cookie](https://github.com/jshttp/cookie) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `cookie` from 0.6.0 to 0.7.1
- [Release notes](https://github.com/jshttp/cookie/releases)
- [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.1)

Updates `express` from 4.21.0 to 4.21.2
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.0...4.21.2)

---
updated-dependencies:
- dependency-name: cookie
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-27 12:17:18 -03:00
Herculino Trotta
023ceb898f Merge pull request #7
github(release): cache build process
2024-12-27 11:25:52 -03:00
Herculino Trotta
1243dddd5d github(release): cache build process 2024-12-27 11:24:56 -03:00
Herculino Trotta
8661fb39e8 github(release): disable provenance when building image 2024-12-27 11:20:44 -03:00
Herculino Trotta
5752606fec docker(prod): update docker-compose.prod.yml to use registry image 2024-12-27 11:20:14 -03:00
Herculino Trotta
7250ce0dbb Merge pull request #6
github(release): drop support for arm besides arm64
2024-12-27 03:24:25 -03:00
Herculino Trotta
b963a3cfb8 github(release): drop support for arm besides arm64 2024-12-27 03:23:51 -03:00
Herculino Trotta
1f14eb011f Merge pull request #5
github: fix "repository name must be lowercase"
2024-12-27 03:13:13 -03:00
Herculino Trotta
265af71ac5 github: fix "repository name must be lowercase" 2024-12-27 03:12:59 -03:00
Herculino Trotta
4c003d4456 Merge pull request #4
dev
2024-12-27 03:09:33 -03:00
Herculino Trotta
d66a2e2856 github: remove changelog creation from release.yml 2 2024-12-27 03:09:16 -03:00
Herculino Trotta
74bf6a655d Merge pull request #3
github: remove changelog creation from release.yml
2024-12-27 03:07:19 -03:00
Herculino Trotta
114cf2622e github: remove changelog creation from release.yml 2024-12-27 03:06:33 -03:00
18 changed files with 525 additions and 282 deletions

View File

@@ -5,40 +5,23 @@ on:
types: [created]
env:
REGISTRY: ghcr.io
IMAGE_NAME: WYGIWYH
IMAGE_NAME: wygiwyh
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Generate changelog
id: changelog
uses: metcalfc/changelog-generator@v4.1.0
with:
myToken: ${{ secrets.GITHUB_TOKEN }}
- name: Update release with changelog
uses: softprops/action-gh-release@v1
with:
body: ${{ steps.changelog.outputs.changelog }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to the Container registry
- name: Log in to Docker Hub
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@@ -52,7 +35,10 @@ jobs:
context: .
file: ./docker/prod/django/Dockerfile
push: true
provenance: false
tags: |
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:latest
${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8,linux/arm64/v8
${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest
${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}:${{ github.event.release.tag_name }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@@ -48,29 +48,28 @@ Frustrated by the lack of comprehensive options, I set out to build **WYGIWYH**
# How To Use
To run this application, you'll need [Git](https://git-scm.com) and [Docker](https://docs.docker.com/engine/install/) with the [docker-compose](https://docs.docker.com/compose/install/).
To run this application, you'll need [Docker](https://docs.docker.com/engine/install/) with [docker-compose](https://docs.docker.com/compose/install/).
From your command line:
> [!NOTE]
> Docker images for this project are currently under development, but manual setup is available now.
```bash
# Clone this repository
$ git clone https://github.com/eitchtee/WYGIWYH
$ mkdir WYGIWYH
# Go into the repository
$ cd WYGIWYH
# Fill the .env file with your configurations
$ cp .env.example .env
$ nano .env # or any other editor you want to use
$ touch docker-compose.yml
$ nano docker-compose.yml
# Paste the contents of https://github.com/eitchtee/WYGIWYH/blob/main/docker-compose.prod.yml and edit according to your needs
# Create docker-compose file
$ cp docker-compose.prod.yml docker-compose.yml
# Fill the .env file with your configurations
$ touch .env
$ nano .env # or any other editor you want to use
# Paste the contents of https://github.com/eitchtee/WYGIWYH/blob/main/.env.example and edit accordingly
# Run the app
$ docker compose up -d --build
$ docker compose up -d
# Create the first admin account
$ docker compose exec -it web python manage.py createsuperuser

View File

@@ -53,6 +53,7 @@ class AccountGroupForm(forms.ModelForm):
class AccountForm(forms.ModelForm):
group = DynamicModelChoiceField(
create_field="name",
label=_("Group"),
model=AccountGroup,
required=False,
@@ -112,6 +113,7 @@ class AccountBalanceForm(forms.Form):
max_digits=42, decimal_places=30, required=False, label=_("New balance")
)
category = DynamicModelChoiceField(
create_field="name",
model=TransactionCategory,
required=False,
label=_("Category"),

View File

@@ -1,6 +1,7 @@
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from django.utils.translation import gettext_lazy as _
from apps.transactions.models import (
TransactionCategory,
@@ -25,13 +26,13 @@ class TransactionCategoryField(serializers.Field):
return TransactionCategory.objects.get(pk=data)
except TransactionCategory.DoesNotExist:
raise serializers.ValidationError(
"Category with this ID does not exist."
_("Category with this ID does not exist.")
)
elif isinstance(data, str):
category, created = TransactionCategory.objects.get_or_create(name=data)
return category
raise serializers.ValidationError(
"Invalid category data. Provide an ID or name."
_("Invalid category data. Provide an ID or name.")
)
@staticmethod
@@ -61,13 +62,13 @@ class TransactionTagField(serializers.Field):
tag = TransactionTag.objects.get(pk=item)
except TransactionTag.DoesNotExist:
raise serializers.ValidationError(
f"Tag with ID {item} does not exist."
_("Tag with this ID does not exist.")
)
elif isinstance(item, str):
tag, created = TransactionTag.objects.get_or_create(name=item)
else:
raise serializers.ValidationError(
"Invalid tag data. Provide an ID or name."
_("Invalid tag data. Provide an ID or name.")
)
tags.append(tag)
return tags
@@ -85,13 +86,13 @@ class TransactionEntityField(serializers.Field):
entity = TransactionEntity.objects.get(pk=item)
except TransactionTag.DoesNotExist:
raise serializers.ValidationError(
f"Entity with ID {item} does not exist."
_("Entity with this ID does not exist.")
)
elif isinstance(item, str):
entity, created = TransactionEntity.objects.get_or_create(name=item)
else:
raise serializers.ValidationError(
"Invalid entity data. Provide an ID or name."
_("Invalid entity data. Provide an ID or name.")
)
entities.append(entity)
return entities

View File

@@ -1,6 +1,7 @@
from django import forms
from django.core.exceptions import ValidationError
from django.db import transaction
from django.utils.translation import gettext_lazy as _
from apps.common.widgets.tom_select import TomSelect, TomSelectMultiple
@@ -8,6 +9,12 @@ from apps.common.widgets.tom_select import TomSelect, TomSelectMultiple
class DynamicModelChoiceField(forms.ModelChoiceField):
def __init__(self, model, *args, **kwargs):
self.model = model
self.to_field_name = kwargs.pop("to_field_name", "pk")
self.create_field = kwargs.pop("create_field", None)
if not self.create_field:
raise ValueError("The 'create_field' parameter is required.")
self.queryset = kwargs.pop("queryset", model.objects.all())
super().__init__(queryset=self.queryset, *args, **kwargs)
self._created_instance = None
@@ -18,8 +25,7 @@ class DynamicModelChoiceField(forms.ModelChoiceField):
if value in self.empty_values:
return None
try:
key = self.to_field_name or "pk"
return self.model.objects.get(**{key: value})
return self.model.objects.get(**{self.to_field_name: value})
except (ValueError, TypeError, self.model.DoesNotExist):
return value # Return the raw value; we'll handle creation in clean()
@@ -49,10 +55,13 @@ class DynamicModelChoiceField(forms.ModelChoiceField):
except self.model.DoesNotExist:
try:
with transaction.atomic():
instance = self.model.objects.create(name=value)
instance, _ = self.model.objects.update_or_create(
**{self.create_field: value}
)
self._created_instance = instance
return instance
except Exception as e:
print(e)
raise ValidationError(
self.error_messages["invalid_choice"], code="invalid_choice"
)
@@ -111,12 +120,12 @@ class DynamicModelMultipleChoiceField(forms.ModelMultipleChoiceField):
"""
try:
with transaction.atomic():
new_instance = self.queryset.model(**{self.create_field: value})
new_instance.full_clean()
new_instance.save()
return new_instance
instance, _ = self.model.objects.update_or_create(
**{self.create_field: value}
)
return instance
except Exception as e:
raise ValidationError(f"Error creating new instance: {str(e)}")
raise ValidationError(_("Error creating new instance"))
def clean(self, value):
"""
@@ -152,6 +161,6 @@ class DynamicModelMultipleChoiceField(forms.ModelMultipleChoiceField):
try:
new_objects.append(self._create_new_instance(new_value))
except ValidationError as e:
raise ValidationError(f"Error creating '{new_value}': {str(e)}")
raise ValidationError(_("Error creating new instance"))
return existing_objects + new_objects

View File

@@ -18,7 +18,7 @@ class MonthYearModelField(models.DateField):
# Set the day to 1
return date.replace(day=1).date()
except ValueError:
raise ValidationError("Invalid date format. Use YYYY-MM.")
raise ValidationError(_("Invalid date format. Use YYYY-MM."))
def formfield(self, **kwargs):
kwargs["widget"] = MonthYearWidget

View File

@@ -8,6 +8,7 @@ from crispy_forms.layout import (
Field,
)
from django import forms
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from apps.accounts.models import Account
@@ -32,9 +33,11 @@ from apps.rules.signals import transaction_created, transaction_updated
class TransactionForm(forms.ModelForm):
category = DynamicModelChoiceField(
create_field="name",
model=TransactionCategory,
required=False,
label=_("Category"),
queryset=TransactionCategory.objects.filter(active=True),
)
tags = DynamicModelMultipleChoiceField(
model=TransactionTag,
@@ -42,6 +45,7 @@ class TransactionForm(forms.ModelForm):
create_field="name",
required=False,
label=_("Tags"),
queryset=TransactionTag.objects.filter(active=True),
)
entities = DynamicModelMultipleChoiceField(
model=TransactionEntity,
@@ -81,6 +85,24 @@ class TransactionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# if editing a transaction display non-archived items and it's own item even if it's archived
if self.instance.id:
self.fields["account"].queryset = Account.objects.filter(
Q(is_archived=False) | Q(transactions=self.instance.id)
).distinct()
self.fields["category"].queryset = TransactionCategory.objects.filter(
Q(active=True) | Q(transaction=self.instance.id)
).distinct()
self.fields["tags"].queryset = TransactionTag.objects.filter(
Q(active=True) | Q(transaction=self.instance.id)
).distinct()
self.fields["entities"].queryset = TransactionEntity.objects.filter(
Q(active=True) | Q(transactions=self.instance.id)
).distinct()
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.form_method = "post"
@@ -181,14 +203,18 @@ class TransferForm(forms.Form):
)
from_category = DynamicModelChoiceField(
create_field="name",
model=TransactionCategory,
required=False,
label=_("Category"),
queryset=TransactionCategory.objects.filter(active=True),
)
to_category = DynamicModelChoiceField(
create_field="name",
model=TransactionCategory,
required=False,
label=_("Category"),
queryset=TransactionCategory.objects.filter(active=True),
)
from_tags = DynamicModelMultipleChoiceField(
@@ -197,6 +223,7 @@ class TransferForm(forms.Form):
create_field="name",
required=False,
label=_("Tags"),
queryset=TransactionTag.objects.filter(active=True),
)
to_tags = DynamicModelMultipleChoiceField(
model=TransactionTag,
@@ -204,6 +231,7 @@ class TransferForm(forms.Form):
create_field="name",
required=False,
label=_("Tags"),
queryset=TransactionTag.objects.filter(active=True),
)
date = forms.DateField(
@@ -299,7 +327,7 @@ class TransferForm(forms.Form):
to_account = cleaned_data.get("to_account")
if from_account == to_account:
raise forms.ValidationError("From and To accounts must be different.")
raise forms.ValidationError(_("From and To accounts must be different."))
return cleaned_data
@@ -358,11 +386,14 @@ class InstallmentPlanForm(forms.ModelForm):
create_field="name",
required=False,
label=_("Tags"),
queryset=TransactionTag.objects.filter(active=True),
)
category = DynamicModelChoiceField(
create_field="name",
model=TransactionCategory,
required=False,
label=_("Category"),
queryset=TransactionCategory.objects.filter(active=True),
)
entities = DynamicModelMultipleChoiceField(
model=TransactionEntity,
@@ -370,6 +401,7 @@ class InstallmentPlanForm(forms.ModelForm):
create_field="name",
required=False,
label=_("Entities"),
queryset=TransactionEntity.objects.filter(active=True),
)
type = forms.ChoiceField(choices=Transaction.Type.choices)
reference_date = MonthYearFormField(label=_("Reference Date"), required=False)
@@ -401,6 +433,24 @@ class InstallmentPlanForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# if editing display non-archived items and it's own item even if it's archived
if self.instance.id:
self.fields["account"].queryset = Account.objects.filter(
Q(is_archived=False) | Q(installmentplan=self.instance.id)
).distinct()
self.fields["category"].queryset = TransactionCategory.objects.filter(
Q(active=True) | Q(installmentplan=self.instance.id)
).distinct()
self.fields["tags"].queryset = TransactionTag.objects.filter(
Q(active=True) | Q(installmentplan=self.instance.id)
).distinct()
self.fields["entities"].queryset = TransactionEntity.objects.filter(
Q(active=True) | Q(installmentplan=self.instance.id)
).distinct()
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.form_method = "post"
@@ -470,7 +520,7 @@ class InstallmentPlanForm(forms.ModelForm):
class TransactionTagForm(forms.ModelForm):
class Meta:
model = TransactionTag
fields = ["name"]
fields = ["name", "active"]
labels = {"name": _("Tag name")}
def __init__(self, *args, **kwargs):
@@ -479,7 +529,7 @@ class TransactionTagForm(forms.ModelForm):
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.form_method = "post"
self.helper.layout = Layout(Field("name", css_class="mb-3"))
self.helper.layout = Layout(Field("name", css_class="mb-3"), Switch("active"))
if self.instance and self.instance.pk:
self.helper.layout.append(
@@ -502,7 +552,7 @@ class TransactionTagForm(forms.ModelForm):
class TransactionEntityForm(forms.ModelForm):
class Meta:
model = TransactionEntity
fields = ["name"]
fields = ["name", "active"]
labels = {"name": _("Entity name")}
def __init__(self, *args, **kwargs):
@@ -511,7 +561,7 @@ class TransactionEntityForm(forms.ModelForm):
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.form_method = "post"
self.helper.layout = Layout(Field("name", css_class="mb-3"))
self.helper.layout = Layout(Field("name"), Switch("active"))
if self.instance and self.instance.pk:
self.helper.layout.append(
@@ -534,7 +584,7 @@ class TransactionEntityForm(forms.ModelForm):
class TransactionCategoryForm(forms.ModelForm):
class Meta:
model = TransactionCategory
fields = ["name", "mute"]
fields = ["name", "mute", "active"]
labels = {"name": _("Category name")}
help_texts = {
"mute": _("Muted categories won't count towards your monthly total")
@@ -546,7 +596,7 @@ class TransactionCategoryForm(forms.ModelForm):
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.form_method = "post"
self.helper.layout = Layout(Field("name", css_class="mb-3"), Switch("mute"))
self.helper.layout = Layout(Field("name"), Switch("mute"), Switch("active"))
if self.instance and self.instance.pk:
self.helper.layout.append(
@@ -578,11 +628,14 @@ class RecurringTransactionForm(forms.ModelForm):
create_field="name",
required=False,
label=_("Tags"),
queryset=TransactionTag.objects.filter(active=True),
)
category = DynamicModelChoiceField(
create_field="name",
model=TransactionCategory,
required=False,
label=_("Category"),
queryset=TransactionCategory.objects.filter(active=True),
)
entities = DynamicModelMultipleChoiceField(
model=TransactionEntity,
@@ -590,6 +643,7 @@ class RecurringTransactionForm(forms.ModelForm):
create_field="name",
required=False,
label=_("Entities"),
queryset=TransactionEntity.objects.filter(active=True),
)
type = forms.ChoiceField(choices=Transaction.Type.choices)
reference_date = MonthYearFormField(label=_("Reference Date"), required=False)
@@ -624,6 +678,25 @@ class RecurringTransactionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# if editing display non-archived items and it's own item even if it's archived
if self.instance.id:
self.fields["account"].queryset = Account.objects.filter(
Q(is_archived=False) | Q(recurringtransaction=self.instance.id)
).distinct()
self.fields["category"].queryset = TransactionCategory.objects.filter(
Q(active=True) | Q(recurringtransaction=self.instance.id)
).distinct()
self.fields["tags"].queryset = TransactionTag.objects.filter(
Q(active=True) | Q(recurringtransaction=self.instance.id)
).distinct()
self.fields["entities"].queryset = TransactionEntity.objects.filter(
Q(active=True) | Q(recurringtransaction=self.instance.id)
).distinct()
self.helper = FormHelper()
self.helper.form_method = "post"
self.helper.form_tag = False

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.1.3 on 2025-01-04 19:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('transactions', '0024_installmentplan_entities_and_more'),
]
operations = [
migrations.AddField(
model_name='transactioncategory',
name='active',
field=models.BooleanField(default=True, help_text="Deactivated categories won't be able to be selected when creating new transactions", verbose_name='Active'),
),
migrations.AddField(
model_name='transactiontag',
name='active',
field=models.BooleanField(default=True, help_text="Deactivated tags won't be able to be selected when creating new transactions", verbose_name='Active'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.3 on 2025-01-04 19:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('transactions', '0025_transactioncategory_active_transactiontag_active'),
]
operations = [
migrations.AddField(
model_name='transactionentity',
name='active',
field=models.BooleanField(default=True, help_text="Deactivated entities won't be able to be selected when creating new transactions", verbose_name='Active'),
),
]

View File

@@ -18,6 +18,13 @@ logger = logging.getLogger()
class TransactionCategory(models.Model):
name = models.CharField(max_length=255, verbose_name=_("Name"), unique=True)
mute = models.BooleanField(default=False, verbose_name=_("Mute"))
active = models.BooleanField(
default=True,
verbose_name=_("Active"),
help_text=_(
"Deactivated categories won't be able to be selected when creating new transactions"
),
)
class Meta:
verbose_name = _("Transaction Category")
@@ -30,6 +37,13 @@ class TransactionCategory(models.Model):
class TransactionTag(models.Model):
name = models.CharField(max_length=255, verbose_name=_("Name"), unique=True)
active = models.BooleanField(
default=True,
verbose_name=_("Active"),
help_text=_(
"Deactivated tags won't be able to be selected when creating new transactions"
),
)
class Meta:
verbose_name = _("Transaction Tags")
@@ -42,8 +56,13 @@ class TransactionTag(models.Model):
class TransactionEntity(models.Model):
name = models.CharField(max_length=255, verbose_name=_("Name"))
# Add any other fields you might want for entities
active = models.BooleanField(
default=True,
verbose_name=_("Active"),
help_text=_(
"Deactivated entities won't be able to be selected when creating new transactions"
),
)
class Meta:
verbose_name = _("Entity")

View File

@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-24 19:06+0000\n"
"PO-Revision-Date: 2024-12-24 16:07-0300\n"
"POT-Creation-Date: 2025-01-04 21:19+0000\n"
"PO-Revision-Date: 2025-01-04 18:22-0300\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: pt_BR\n"
@@ -23,22 +23,22 @@ msgstr ""
msgid "Group name"
msgstr "Nome do grupo"
#: apps/accounts/forms.py:40 apps/accounts/forms.py:95
#: apps/accounts/forms.py:40 apps/accounts/forms.py:96
#: apps/currencies/forms.py:51 apps/currencies/forms.py:90 apps/dca/forms.py:40
#: apps/dca/forms.py:93 apps/rules/forms.py:45 apps/rules/forms.py:87
#: apps/transactions/forms.py:123 apps/transactions/forms.py:445
#: apps/transactions/forms.py:488 apps/transactions/forms.py:520
#: apps/transactions/forms.py:555 apps/transactions/forms.py:668
#: apps/transactions/forms.py:145 apps/transactions/forms.py:495
#: apps/transactions/forms.py:538 apps/transactions/forms.py:570
#: apps/transactions/forms.py:605 apps/transactions/forms.py:741
msgid "Update"
msgstr "Atualizar"
#: apps/accounts/forms.py:48 apps/accounts/forms.py:103
#: apps/accounts/forms.py:48 apps/accounts/forms.py:104
#: apps/common/widgets/tom_select.py:12 apps/currencies/forms.py:59
#: apps/currencies/forms.py:98 apps/dca/forms.py:48 apps/dca/forms.py:102
#: apps/rules/forms.py:53 apps/rules/forms.py:95 apps/transactions/forms.py:132
#: apps/transactions/forms.py:453 apps/transactions/forms.py:496
#: apps/transactions/forms.py:528 apps/transactions/forms.py:563
#: apps/transactions/forms.py:676
#: apps/rules/forms.py:53 apps/rules/forms.py:95 apps/transactions/forms.py:154
#: apps/transactions/forms.py:503 apps/transactions/forms.py:546
#: apps/transactions/forms.py:578 apps/transactions/forms.py:613
#: apps/transactions/forms.py:749
#: templates/account_groups/fragments/list.html:9
#: templates/accounts/fragments/list.html:9
#: templates/categories/fragments/list.html:9
@@ -54,35 +54,35 @@ msgstr "Atualizar"
msgid "Add"
msgstr "Adicionar"
#: apps/accounts/forms.py:56 templates/accounts/fragments/list.html:26
#: apps/accounts/forms.py:57 templates/accounts/fragments/list.html:26
msgid "Group"
msgstr "Grupo da Conta"
#: apps/accounts/forms.py:112
#: apps/accounts/forms.py:113
msgid "New balance"
msgstr "Novo saldo"
#: apps/accounts/forms.py:117 apps/rules/models.py:27
#: apps/transactions/forms.py:37 apps/transactions/forms.py:186
#: apps/transactions/forms.py:191 apps/transactions/forms.py:365
#: apps/transactions/forms.py:585 apps/transactions/models.py:90
#: apps/transactions/models.py:209 apps/transactions/models.py:384
#: apps/accounts/forms.py:119 apps/rules/models.py:27
#: apps/transactions/forms.py:39 apps/transactions/forms.py:209
#: apps/transactions/forms.py:216 apps/transactions/forms.py:395
#: apps/transactions/forms.py:637 apps/transactions/models.py:109
#: apps/transactions/models.py:228 apps/transactions/models.py:403
msgid "Category"
msgstr "Categoria"
#: apps/accounts/forms.py:124 apps/rules/models.py:28
#: apps/transactions/filters.py:65 apps/transactions/forms.py:44
#: apps/transactions/forms.py:199 apps/transactions/forms.py:206
#: apps/transactions/forms.py:360 apps/transactions/forms.py:580
#: apps/transactions/models.py:96 apps/transactions/models.py:211
#: apps/transactions/models.py:388 templates/includes/navbar.html:98
#: apps/accounts/forms.py:126 apps/rules/models.py:28
#: apps/transactions/filters.py:73 apps/transactions/forms.py:47
#: apps/transactions/forms.py:225 apps/transactions/forms.py:233
#: apps/transactions/forms.py:388 apps/transactions/forms.py:630
#: apps/transactions/models.py:115 apps/transactions/models.py:230
#: apps/transactions/models.py:407 templates/includes/navbar.html:98
#: templates/tags/fragments/list.html:5 templates/tags/pages/index.html:4
msgid "Tags"
msgstr "Tags"
#: apps/accounts/models.py:9 apps/accounts/models.py:21 apps/dca/models.py:14
#: apps/rules/models.py:9 apps/transactions/models.py:19
#: apps/transactions/models.py:32 apps/transactions/models.py:44
#: apps/transactions/models.py:39 apps/transactions/models.py:58
#: templates/account_groups/fragments/list.html:25
#: templates/accounts/fragments/list.html:25
#: templates/categories/fragments/list.html:25
@@ -139,16 +139,17 @@ msgstr ""
"Contas arquivadas não aparecem nem contam para o seu patrimônio líquido"
#: apps/accounts/models.py:59 apps/rules/models.py:19
#: apps/transactions/forms.py:55 apps/transactions/forms.py:352
#: apps/transactions/forms.py:572 apps/transactions/models.py:65
#: apps/transactions/models.py:169 apps/transactions/models.py:366
#: apps/transactions/forms.py:59 apps/transactions/forms.py:380
#: apps/transactions/forms.py:622 apps/transactions/models.py:84
#: apps/transactions/models.py:188 apps/transactions/models.py:385
msgid "Account"
msgstr "Conta"
#: apps/accounts/models.py:60 apps/transactions/filters.py:51
#: apps/accounts/models.py:60 apps/transactions/filters.py:52
#: templates/accounts/fragments/list.html:5
#: templates/accounts/pages/index.html:4 templates/includes/navbar.html:104
#: templates/includes/navbar.html:106
#: templates/transactions/fragments/summary.html:9
msgid "Accounts"
msgstr "Contas"
@@ -188,16 +189,45 @@ msgstr "Reconciliação do saldo"
msgid "Account balances have been reconciled successfully"
msgstr "Os saldos das contas foram conciliados com sucesso"
#: apps/api/fields/transactions.py:29
msgid "Category with this ID does not exist."
msgstr "Categoria com esse ID não existe."
#: apps/api/fields/transactions.py:35
msgid "Invalid category data. Provide an ID or name."
msgstr "Dados da categoria inválidos. Forneça um ID ou nome."
#: apps/api/fields/transactions.py:65
msgid "Tag with this ID does not exist."
msgstr "Tag com esse ID não existe."
#: apps/api/fields/transactions.py:71
msgid "Invalid tag data. Provide an ID or name."
msgstr "Dados da tag inválidos. Forneça um ID ou nome."
#: apps/api/fields/transactions.py:89
msgid "Entity with this ID does not exist."
msgstr "Entidade com esse ID não existe."
#: apps/api/fields/transactions.py:95
msgid "Invalid entity data. Provide an ID or name."
msgstr "Dados da entidade inválidos. Forneça um ID ou nome."
#: apps/api/serializers/transactions.py:96
msgid "Either 'date' or 'reference_date' must be provided."
msgstr "É necessário fornecer “date” ou “reference_date”."
#: apps/common/fields/forms/dynamic_select.py:128
#: apps/common/fields/forms/dynamic_select.py:164
msgid "Error creating new instance"
msgstr "Erro criando nova instância"
#: apps/common/fields/forms/grouped_select.py:24
#: apps/common/widgets/tom_select.py:91 apps/common/widgets/tom_select.py:94
msgid "Ungrouped"
msgstr "Não agrupado"
#: apps/common/fields/month_year.py:45
#: apps/common/fields/month_year.py:21 apps/common/fields/month_year.py:45
msgid "Invalid date format. Use YYYY-MM."
msgstr "Formato de data inválido. Use AAAA-MM."
@@ -314,9 +344,11 @@ msgstr "Nome da Moeda"
msgid "Decimal Places"
msgstr "Casas Decimais"
#: apps/currencies/models.py:33 templates/currencies/fragments/list.html:5
#: apps/currencies/models.py:33 apps/transactions/filters.py:59
#: templates/currencies/fragments/list.html:5
#: templates/currencies/pages/index.html:4 templates/includes/navbar.html:112
#: templates/includes/navbar.html:114
#: templates/transactions/fragments/summary.html:6
msgid "Currencies"
msgstr "Moedas"
@@ -383,8 +415,8 @@ msgid "Payment Currency"
msgstr "Moeda de pagamento"
#: apps/dca/models.py:27 apps/dca/models.py:179 apps/rules/models.py:26
#: apps/transactions/forms.py:222 apps/transactions/models.py:86
#: apps/transactions/models.py:218 apps/transactions/models.py:394
#: apps/transactions/forms.py:250 apps/transactions/models.py:105
#: apps/transactions/models.py:237 apps/transactions/models.py:413
msgid "Notes"
msgstr "Notas"
@@ -401,7 +433,7 @@ msgid "Strategy"
msgstr "Estratégia"
#: apps/dca/models.py:156 apps/rules/models.py:22
#: apps/transactions/forms.py:210 apps/transactions/models.py:75
#: apps/transactions/forms.py:238 apps/transactions/models.py:94
#: templates/dca/fragments/strategy/details.html:52
#: templates/exchange_rates/fragments/table.html:10
msgid "Date"
@@ -480,8 +512,8 @@ msgid "A value for this field already exists in the rule."
msgstr "Já existe um valor para esse campo na regra."
#: apps/rules/models.py:10 apps/rules/models.py:25
#: apps/transactions/forms.py:214 apps/transactions/models.py:85
#: apps/transactions/models.py:176 apps/transactions/models.py:380
#: apps/transactions/forms.py:242 apps/transactions/models.py:104
#: apps/transactions/models.py:195 apps/transactions/models.py:399
msgid "Description"
msgstr "Descrição"
@@ -489,33 +521,33 @@ msgstr "Descrição"
msgid "Trigger"
msgstr "Gatilho"
#: apps/rules/models.py:20 apps/transactions/models.py:72
#: apps/transactions/models.py:174 apps/transactions/models.py:372
#: apps/rules/models.py:20 apps/transactions/models.py:91
#: apps/transactions/models.py:193 apps/transactions/models.py:391
msgid "Type"
msgstr "Tipo"
#: apps/rules/models.py:21 apps/transactions/filters.py:21
#: apps/transactions/models.py:74
#: apps/rules/models.py:21 apps/transactions/filters.py:22
#: apps/transactions/models.py:93
msgid "Paid"
msgstr "Pago"
#: apps/rules/models.py:23 apps/transactions/forms.py:58
#: apps/transactions/forms.py:213 apps/transactions/forms.py:375
#: apps/transactions/forms.py:595 apps/transactions/models.py:76
#: apps/transactions/models.py:192 apps/transactions/models.py:396
#: apps/rules/models.py:23 apps/transactions/forms.py:62
#: apps/transactions/forms.py:241 apps/transactions/forms.py:407
#: apps/transactions/forms.py:649 apps/transactions/models.py:95
#: apps/transactions/models.py:211 apps/transactions/models.py:415
msgid "Reference Date"
msgstr "Data de Referência"
#: apps/rules/models.py:24 apps/transactions/models.py:81
#: apps/transactions/models.py:377
#: apps/rules/models.py:24 apps/transactions/models.py:100
#: apps/transactions/models.py:396
msgid "Amount"
msgstr "Quantia"
#: apps/rules/models.py:29 apps/transactions/filters.py:72
#: apps/transactions/forms.py:51 apps/transactions/forms.py:372
#: apps/transactions/forms.py:592 apps/transactions/models.py:50
#: apps/transactions/models.py:101 apps/transactions/models.py:214
#: apps/transactions/models.py:391 templates/entities/fragments/list.html:5
#: apps/rules/models.py:29 apps/transactions/filters.py:80
#: apps/transactions/forms.py:55 apps/transactions/forms.py:403
#: apps/transactions/forms.py:645 apps/transactions/models.py:69
#: apps/transactions/models.py:120 apps/transactions/models.py:233
#: apps/transactions/models.py:410 templates/entities/fragments/list.html:5
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:100
msgid "Entities"
msgstr "Entidades"
@@ -560,60 +592,60 @@ msgstr "Ação atualizada com sucesso"
msgid "Action deleted successfully"
msgstr "Ação apagada com sucesso"
#: apps/transactions/filters.py:22 templates/includes/navbar.html:45
#: apps/transactions/filters.py:23 templates/includes/navbar.html:45
msgid "Projected"
msgstr "Previsto"
#: apps/transactions/filters.py:39
#: apps/transactions/filters.py:40
msgid "Content"
msgstr "Conteúdo"
#: apps/transactions/filters.py:45
#: apps/transactions/filters.py:46
msgid "Transaction Type"
msgstr "Tipo de Transação"
#: apps/transactions/filters.py:58 templates/categories/fragments/list.html:5
#: apps/transactions/filters.py:66 templates/categories/fragments/list.html:5
#: templates/categories/pages/index.html:4 templates/includes/navbar.html:96
msgid "Categories"
msgstr "Categorias"
#: apps/transactions/filters.py:83
#: apps/transactions/filters.py:91
msgid "Date from"
msgstr "Data de"
#: apps/transactions/filters.py:89 apps/transactions/filters.py:99
#: apps/transactions/filters.py:97 apps/transactions/filters.py:107
msgid "Until"
msgstr "Até"
#: apps/transactions/filters.py:94
#: apps/transactions/filters.py:102
msgid "Reference date from"
msgstr "Data de Referência de"
#: apps/transactions/filters.py:104
#: apps/transactions/filters.py:112
msgid "Amount min"
msgstr "Quantia miníma"
#: apps/transactions/filters.py:109
#: apps/transactions/filters.py:117
msgid "Amount max"
msgstr "Quantia máxima"
#: apps/transactions/forms.py:162
#: apps/transactions/forms.py:184
msgid "From Account"
msgstr "Conta de origem"
#: apps/transactions/forms.py:167
#: apps/transactions/forms.py:189
msgid "To Account"
msgstr "Conta de destino"
#: apps/transactions/forms.py:174
#: apps/transactions/forms.py:196
msgid "From Amount"
msgstr "Quantia de origem"
#: apps/transactions/forms.py:179
#: apps/transactions/forms.py:201
msgid "To Amount"
msgstr "Quantia de destino"
#: apps/transactions/forms.py:287
#: apps/transactions/forms.py:315
#: templates/calendar_view/pages/calendar.html:84
#: templates/monthly_overview/pages/overview.html:84
#: templates/yearly_overview/pages/overview_by_account.html:79
@@ -621,23 +653,27 @@ msgstr "Quantia de destino"
msgid "Transfer"
msgstr "Transferir"
#: apps/transactions/forms.py:474
#: apps/transactions/forms.py:330
msgid "From and To accounts must be different."
msgstr "As contas De e Para devem ser diferentes."
#: apps/transactions/forms.py:524
msgid "Tag name"
msgstr "Nome da Tag"
#: apps/transactions/forms.py:506
#: apps/transactions/forms.py:556
msgid "Entity name"
msgstr "Nome da entidade"
#: apps/transactions/forms.py:538
#: apps/transactions/forms.py:588
msgid "Category name"
msgstr "Nome da Categoria"
#: apps/transactions/forms.py:540
#: apps/transactions/forms.py:590
msgid "Muted categories won't count towards your monthly total"
msgstr "As categorias silenciadas não serão contabilizadas em seu total mensal"
#: apps/transactions/forms.py:687
#: apps/transactions/forms.py:760
msgid "End date should be after the start date"
msgstr "Data final deve ser após data inicial"
@@ -645,32 +681,61 @@ msgstr "Data final deve ser após data inicial"
msgid "Mute"
msgstr "Silenciada"
#: apps/transactions/models.py:23
#: apps/transactions/models.py:23 apps/transactions/models.py:42
#: apps/transactions/models.py:61
#: templates/recurring_transactions/fragments/list.html:21
msgid "Active"
msgstr "Ativo"
#: apps/transactions/models.py:25
msgid ""
"Deactivated categories won't be able to be selected when creating new "
"transactions"
msgstr ""
"As categorias desativadas não poderão ser selecionadas ao criar novas "
"transações"
#: apps/transactions/models.py:30
msgid "Transaction Category"
msgstr "Categoria da Transação"
#: apps/transactions/models.py:24
#: apps/transactions/models.py:31
msgid "Transaction Categories"
msgstr "Categorias da Trasanção"
#: apps/transactions/models.py:35 apps/transactions/models.py:36
#: apps/transactions/models.py:44
msgid ""
"Deactivated tags won't be able to be selected when creating new transactions"
msgstr ""
"As tags desativadas não poderão ser selecionadas ao criar novas transações"
#: apps/transactions/models.py:49 apps/transactions/models.py:50
msgid "Transaction Tags"
msgstr "Tags da Transação"
#: apps/transactions/models.py:49
#: apps/transactions/models.py:63
msgid ""
"Deactivated entities won't be able to be selected when creating new "
"transactions"
msgstr ""
"As entidades desativadas não poderão ser selecionadas ao criar novas "
"transações"
#: apps/transactions/models.py:68
msgid "Entity"
msgstr "Entidade"
#: apps/transactions/models.py:59
#: apps/transactions/models.py:78
#: templates/calendar_view/pages/calendar.html:54
#: templates/monthly_overview/fragments/monthly_summary.html:39
#: templates/monthly_overview/pages/overview.html:54
#: templates/transactions/fragments/summary.html:17
#: templates/yearly_overview/pages/overview_by_account.html:49
#: templates/yearly_overview/pages/overview_by_currency.html:51
msgid "Income"
msgstr "Renda"
#: apps/transactions/models.py:60
#: apps/transactions/models.py:79
#: templates/calendar_view/pages/calendar.html:62
#: templates/monthly_overview/pages/overview.html:62
#: templates/yearly_overview/pages/overview_by_account.html:57
@@ -678,19 +743,19 @@ msgstr "Renda"
msgid "Expense"
msgstr "Despesa"
#: apps/transactions/models.py:112 apps/transactions/models.py:221
#: apps/transactions/models.py:131 apps/transactions/models.py:240
msgid "Installment Plan"
msgstr "Parcelamento"
#: apps/transactions/models.py:121 apps/transactions/models.py:417
#: apps/transactions/models.py:140 apps/transactions/models.py:436
msgid "Recurring Transaction"
msgstr "Transação Recorrente"
#: apps/transactions/models.py:125
#: apps/transactions/models.py:144
msgid "Transaction"
msgstr "Transação"
#: apps/transactions/models.py:126 templates/includes/navbar.html:53
#: apps/transactions/models.py:145 templates/includes/navbar.html:53
#: templates/includes/navbar.html:94
#: templates/recurring_transactions/fragments/list_transactions.html:5
#: templates/recurring_transactions/fragments/table.html:37
@@ -698,95 +763,95 @@ msgstr "Transação"
msgid "Transactions"
msgstr "Transações"
#: apps/transactions/models.py:163
#: apps/transactions/models.py:182
msgid "Yearly"
msgstr "Anual"
#: apps/transactions/models.py:164 apps/users/models.py:26
#: apps/transactions/models.py:183 apps/users/models.py:26
#: templates/includes/navbar.html:25
msgid "Monthly"
msgstr "Mensal"
#: apps/transactions/models.py:165
#: apps/transactions/models.py:184
msgid "Weekly"
msgstr "Semanal"
#: apps/transactions/models.py:166
#: apps/transactions/models.py:185
msgid "Daily"
msgstr "Diária"
#: apps/transactions/models.py:179
#: apps/transactions/models.py:198
msgid "Number of Installments"
msgstr "Número de Parcelas"
#: apps/transactions/models.py:184
#: apps/transactions/models.py:203
msgid "Installment Start"
msgstr "Parcela inicial"
#: apps/transactions/models.py:185
#: apps/transactions/models.py:204
msgid "The installment number to start counting from"
msgstr "O número da parcela a partir do qual se inicia a contagem"
#: apps/transactions/models.py:190 apps/transactions/models.py:400
#: apps/transactions/models.py:209 apps/transactions/models.py:419
msgid "Start Date"
msgstr "Data de Início"
#: apps/transactions/models.py:194 apps/transactions/models.py:401
#: apps/transactions/models.py:213 apps/transactions/models.py:420
msgid "End Date"
msgstr "Data Final"
#: apps/transactions/models.py:199
#: apps/transactions/models.py:218
msgid "Recurrence"
msgstr "Recorrência"
#: apps/transactions/models.py:202
#: apps/transactions/models.py:221
msgid "Installment Amount"
msgstr "Valor da Parcela"
#: apps/transactions/models.py:222 templates/includes/navbar.html:62
#: apps/transactions/models.py:241 templates/includes/navbar.html:62
#: templates/installment_plans/fragments/list.html:5
#: templates/installment_plans/pages/index.html:4
msgid "Installment Plans"
msgstr "Parcelamentos"
#: apps/transactions/models.py:359
#: apps/transactions/models.py:378
msgid "day(s)"
msgstr "dia(s)"
#: apps/transactions/models.py:360
#: apps/transactions/models.py:379
msgid "week(s)"
msgstr "semana(s)"
#: apps/transactions/models.py:361
#: apps/transactions/models.py:380
msgid "month(s)"
msgstr "mês(es)"
#: apps/transactions/models.py:362
#: apps/transactions/models.py:381
msgid "year(s)"
msgstr "ano(s)"
#: apps/transactions/models.py:364
#: apps/transactions/models.py:383
#: templates/recurring_transactions/fragments/list.html:24
msgid "Paused"
msgstr "Pausado"
#: apps/transactions/models.py:403
#: apps/transactions/models.py:422
msgid "Recurrence Type"
msgstr "Tipo de recorrência"
#: apps/transactions/models.py:406
#: apps/transactions/models.py:425
msgid "Recurrence Interval"
msgstr "Intervalo de recorrência"
#: apps/transactions/models.py:410
#: apps/transactions/models.py:429
msgid "Last Generated Date"
msgstr "Última data gerada"
#: apps/transactions/models.py:413
#: apps/transactions/models.py:432
msgid "Last Generated Reference Date"
msgstr "Última data de referência gerada"
#: apps/transactions/models.py:418 templates/includes/navbar.html:64
#: apps/transactions/models.py:437 templates/includes/navbar.html:64
#: templates/recurring_transactions/fragments/list.html:5
#: templates/recurring_transactions/pages/index.html:4
msgid "Recurring Transactions"
@@ -878,19 +943,19 @@ msgstr "Tag atualizada com sucesso"
msgid "Tag deleted successfully"
msgstr "Tag apagada com sucesso"
#: apps/transactions/views/transactions.py:39
#: apps/transactions/views/transactions.py:45
msgid "Transaction added successfully"
msgstr "Transação adicionada com sucesso"
#: apps/transactions/views/transactions.py:70
#: apps/transactions/views/transactions.py:76
msgid "Transaction updated successfully"
msgstr "Transação atualizada com sucesso"
#: apps/transactions/views/transactions.py:95
#: apps/transactions/views/transactions.py:101
msgid "Transaction deleted successfully"
msgstr "Transação apagada com sucesso"
#: apps/transactions/views/transactions.py:121
#: apps/transactions/views/transactions.py:127
msgid "Transfer added successfully"
msgstr "Transferência adicionada com sucesso"
@@ -1272,7 +1337,7 @@ msgstr "Marcar como não pago"
msgid "Yes, delete them!"
msgstr "Sim, apague!"
#: templates/cotton/ui/transactions_action_bar.html:73
#: templates/cotton/ui/transactions_action_bar.html:81
msgid "copied!"
msgstr "copiado!"
@@ -1575,6 +1640,10 @@ msgstr "Isso excluirá o parcelamento e todas as transações associadas a ele"
msgid "No installment plans"
msgstr "Nenhum parcelamento"
#: templates/mini_tools/currency_converter/currency_converter.html:56
msgid "Invert"
msgstr "Inverter"
#: templates/mini_tools/unit_price_calculator.html:27
#: templates/mini_tools/unit_price_calculator.html:100
#: templates/mini_tools/unit_price_calculator.html:125
@@ -1614,24 +1683,33 @@ msgstr "Esse é o total final dividido pelos dias restantes do mês"
#: templates/monthly_overview/fragments/monthly_summary.html:42
#: templates/monthly_overview/fragments/monthly_summary.html:106
#: templates/monthly_overview/fragments/monthly_summary.html:170
#: templates/transactions/fragments/summary.html:20
#: templates/transactions/fragments/summary.html:84
#: templates/transactions/fragments/summary.html:148
msgid "current"
msgstr "atual"
#: templates/monthly_overview/fragments/monthly_summary.html:72
#: templates/monthly_overview/fragments/monthly_summary.html:136
#: templates/monthly_overview/fragments/monthly_summary.html:199
#: templates/transactions/fragments/summary.html:50
#: templates/transactions/fragments/summary.html:114
#: templates/transactions/fragments/summary.html:177
msgid "projected"
msgstr "previsto"
#: templates/monthly_overview/fragments/monthly_summary.html:103
#: templates/transactions/fragments/summary.html:81
msgid "Expenses"
msgstr "Despesas"
#: templates/monthly_overview/fragments/monthly_summary.html:167
#: templates/transactions/fragments/summary.html:145
msgid "Total"
msgstr "Total"
#: templates/monthly_overview/fragments/monthly_summary.html:256
#: templates/transactions/fragments/summary.html:234
msgid "Distribution"
msgstr "Distribuição"
@@ -1672,16 +1750,16 @@ msgstr "Patrimônio Previsto"
msgid "By currency"
msgstr "Por moeda"
#: templates/net_worth/net_worth.html:52
#: templates/net_worth/net_worth.html:65
#: templates/yearly_overview/pages/overview_by_account.html:7
msgid "By account"
msgstr "Por conta"
#: templates/net_worth/net_worth.html:153
#: templates/net_worth/net_worth.html:172
msgid "Evolution by currency"
msgstr "Evolução por moeda"
#: templates/net_worth/net_worth.html:208
#: templates/net_worth/net_worth.html:236
msgid "Evolution by account"
msgstr "Evolução por conta"
@@ -1693,10 +1771,6 @@ msgstr "Adicionar transação recorrente"
msgid "Edit recurring transaction"
msgstr "Editar transação recorrente"
#: templates/recurring_transactions/fragments/list.html:21
msgid "Active"
msgstr "Ativo"
#: templates/recurring_transactions/fragments/table.html:47
msgid "Unpause"
msgstr "Despausar"
@@ -1831,6 +1905,54 @@ msgstr "Editar transação"
msgid "No transactions found"
msgstr "Nenhuma transação encontrada"
#: templates/transactions/fragments/summary.html:255
#: templates/yearly_overview/fragments/account_data.html:14
#: templates/yearly_overview/fragments/currency_data.html:14
msgid "projected income"
msgstr "renda prevista"
#: templates/transactions/fragments/summary.html:277
#: templates/yearly_overview/fragments/account_data.html:36
#: templates/yearly_overview/fragments/currency_data.html:36
msgid "projected expenses"
msgstr "despesas previstas"
#: templates/transactions/fragments/summary.html:301
#: templates/yearly_overview/fragments/account_data.html:60
#: templates/yearly_overview/fragments/currency_data.html:60
msgid "projected total"
msgstr "total previsto"
#: templates/transactions/fragments/summary.html:326
#: templates/yearly_overview/fragments/account_data.html:85
#: templates/yearly_overview/fragments/currency_data.html:85
msgid "current income"
msgstr "renda atual"
#: templates/transactions/fragments/summary.html:348
#: templates/yearly_overview/fragments/account_data.html:107
#: templates/yearly_overview/fragments/currency_data.html:107
msgid "current expenses"
msgstr "despesas atuais"
#: templates/transactions/fragments/summary.html:370
#: templates/yearly_overview/fragments/account_data.html:129
#: templates/yearly_overview/fragments/currency_data.html:129
msgid "current total"
msgstr "total atual"
#: templates/transactions/fragments/summary.html:396
#: templates/yearly_overview/fragments/account_data.html:155
#: templates/yearly_overview/fragments/currency_data.html:155
msgid "final total"
msgstr "total final"
#: templates/transactions/fragments/summary.html:426
#: templates/yearly_overview/fragments/account_data.html:185
#: templates/yearly_overview/fragments/currency_data.html:184
msgid "No information to display"
msgstr "Não há informação para mostrar"
#: templates/transactions/fragments/transfer.html:5
msgid "New transfer"
msgstr "Nova transferência"
@@ -1855,46 +1977,6 @@ msgstr "Reproduzir sons"
msgid "Show amounts"
msgstr "Mostrar valores"
#: templates/yearly_overview/fragments/account_data.html:14
#: templates/yearly_overview/fragments/currency_data.html:14
msgid "projected income"
msgstr "renda prevista"
#: templates/yearly_overview/fragments/account_data.html:36
#: templates/yearly_overview/fragments/currency_data.html:36
msgid "projected expenses"
msgstr "despesas previstas"
#: templates/yearly_overview/fragments/account_data.html:60
#: templates/yearly_overview/fragments/currency_data.html:60
msgid "projected total"
msgstr "total previsto"
#: templates/yearly_overview/fragments/account_data.html:85
#: templates/yearly_overview/fragments/currency_data.html:85
msgid "current income"
msgstr "renda atual"
#: templates/yearly_overview/fragments/account_data.html:107
#: templates/yearly_overview/fragments/currency_data.html:107
msgid "current expenses"
msgstr "despesas atuais"
#: templates/yearly_overview/fragments/account_data.html:129
#: templates/yearly_overview/fragments/currency_data.html:129
msgid "current total"
msgstr "total atual"
#: templates/yearly_overview/fragments/account_data.html:155
#: templates/yearly_overview/fragments/currency_data.html:155
msgid "final total"
msgstr "total final"
#: templates/yearly_overview/fragments/account_data.html:184
#: templates/yearly_overview/fragments/currency_data.html:184
msgid "No information to display"
msgstr "Não há informação para mostrar"
#: templates/yearly_overview/pages/overview_by_account.html:7
#: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "Yearly Overview"

View File

@@ -1,5 +1,5 @@
{% load i18n %}
<div class="transaction d-flex my-1">
<div class="transaction d-flex my-1 {% if transaction.type == "EX" %}expense{% else %}income{% endif %}">
{% if not disable_selection %}
<label class="px-3 d-flex align-items-center justify-content-center">
<input class="form-check-input" type="checkbox" name="transactions" value="{{ transaction.id }}" id="check-{{ transaction.id }}" aria-label="{% translate 'Select' %}" hx-preserve>

View File

@@ -57,15 +57,23 @@
</button>
<div class="vr mx-3 tw-align-middle"></div>
<span _="on selected_transactions_updated from #actions-bar
set total to 0.0
for amt in <.transaction:has(input[name='transactions']:checked) .main-amount .amount/>
set realTotal to 0.0
set flatTotal to 0.0
for transaction in <.transaction:has(input[name='transactions']:checked)/>
set amt to first <.main-amount .amount/> in transaction
set amountValue to parseFloat(amt.getAttribute('data-amount'))
if not isNaN(amountValue)
set total to total + (amountValue * 100)
set flatTotal to flatTotal + (amountValue * 100)
if transaction match .income
set realTotal to realTotal + (amountValue * 100)
else
set realTotal to realTotal - (amountValue * 100)
end
end
end
set total to total / 100
put total.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into me
set realTotal to realTotal / 100
put realTotal.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into me
end
on click
set original_value to my innerText

View File

@@ -13,7 +13,7 @@
<div>{% translate 'Currency Converter' %}</div>
</div>
<div class="row">
<div class="col-5">
<div class="col-12 col-lg-5">
<div>
<input class="form-control form-control-lg mb-3"
type="text"
@@ -25,13 +25,14 @@
</div>
<div>{{ form.from_currency|as_crispy_field }}</div>
</div>
<div class="col text-primary tw-flex tw-items-center tw-justify-center">
<div class="col text-primary tw-flex tw-items-center tw-justify-center my-3 my-lg-0">
<i class="fa-solid fa-equals"></i>
</div>
<div class="col-5">
<div class="col-12 col-lg-5">
<div hx-get="{% url 'currency_converter_convert' %}"
hx-trigger="input from:#from_value, input from:#id_from_currency, input from:#id_to_currency"
hx-include="#from_value, #id_from_currency, #id_to_currency">
hx-trigger="input from:#from_value, input from:#id_from_currency, input from:#id_to_currency, updated"
hx-include="#from_value, #id_from_currency, #id_to_currency"
id="result">
<input class="form-control form-control-lg mb-3"
type="text"
name="to_value"
@@ -41,5 +42,19 @@
<div>{{ form.to_currency|as_crispy_field }}</div>
</div>
</div>
<div class="row">
<div class="tw-cursor-pointer text-primary text-end"
_="on click
set from_value to #id_from_currency's value
set to_value to #id_to_currency's value
set #id_from_currency's value to to_value
set #id_to_currency's value to from_value
call #id_from_currency.tomselect.sync()
call #id_to_currency.tomselect.sync()
trigger updated on #result
end">
<i class="fa-solid fa-rotate me-2"></i><span>{% trans 'Invert' %}</span>
</div>
</div>
</div>
{% endblock %}

View File

@@ -9,7 +9,7 @@
{% block title %}{% if type == "current" %}{% translate 'Current Net Worth' %}{% else %}{% translate 'Projected Net Worth' %}{% endif %}{% endblock %}
{% block content %}
<div class="container px-md-3 py-3">
<div class="container px-md-3 py-3" _="on load call initializeAccountChart() then initializeCurrencyChart()">
<div class="row gx-xl-4 gy-3 mb-4">
<div class="col-12 col-xl-5">
<div class="row row-cols-1 g-4">
@@ -52,7 +52,7 @@
</div>
</div>
<div class="col-12 col-xl-7">
<div class="chart-container position-relativo tw-min-h-[40vh] tw-h-full">
<div class="chart-container position-relative tw-min-h-[40vh] tw-h-full">
<canvas id="currencyBalanceChart"></canvas>
</div>
</div>
@@ -136,7 +136,7 @@
</div>
</div>
<div class="col-12 col-xl-7">
<div class="chart-container position-relativo tw-min-h-[40vh] tw-h-full">
<div class="chart-container position-relative tw-min-h-[40vh] tw-h-full">
<canvas id="accountBalanceChart"></canvas>
</div>
</div>
@@ -144,13 +144,19 @@
</div>
<script>
document.body.addEventListener('htmx:load', function (evt) {
var currencyChart;
function initializeCurrencyChart() {
// Destroy existing chart if it exists
if (currencyChart) {
currencyChart.destroy();
}
var chartData = JSON.parse('{{ chart_data_currency_json|safe }}');
var currencies = {{ currencies|safe }};
var ctx = document.getElementById('currencyBalanceChart').getContext('2d');
new Chart(ctx, {
currencyChart = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
@@ -197,17 +203,23 @@
}
}
});
});
}
</script>
<script>
document.body.addEventListener('htmx:load', function (evt) {
<script id="accountBalanceChartScript">
var accountChart;
function initializeAccountChart() {
// Destroy existing chart if it exists
if (accountChart) {
accountChart.destroy();
}
var chartData = JSON.parse('{{ chart_data_accounts_json|safe }}');
var accounts = {{ accounts|safe }};
var ctx = document.getElementById('accountBalanceChart').getContext('2d');
new Chart(ctx, {
accountChart = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
@@ -256,43 +268,38 @@
}
}
});
});
}
</script>
<script type="text/hyperscript">
def showOnlyAccountDataset(datasetName)
set chart to Chart.getChart('accountBalanceChart')
for dataset in chart.data.datasets
for dataset in accountChart.data.datasets
set isMatch to dataset.label is datasetName
call chart.setDatasetVisibility(chart.data.datasets.indexOf(dataset), isMatch)
call accountChart.setDatasetVisibility(accountChart.data.datasets.indexOf(dataset), isMatch)
end
call chart.update()
call accountChart.update()
end
def showOnlyCurrencyDataset(datasetName)
set chart to Chart.getChart('currencyBalanceChart')
for dataset in chart.data.datasets
for dataset in currencyChart.data.datasets
set isMatch to dataset.label is datasetName
call chart.setDatasetVisibility(chart.data.datasets.indexOf(dataset), isMatch)
call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), isMatch)
end
call chart.update()
call currencyChart.update()
end
def showAllDatasetsAccount()
set chart to Chart.getChart('accountBalanceChart')
for dataset in chart.data.datasets
call chart.setDatasetVisibility(chart.data.datasets.indexOf(dataset), true)
for dataset in accountChart.data.datasets
call accountChart.setDatasetVisibility(accountChart.data.datasets.indexOf(dataset), true)
end
call chart.update()
call accountChart.update()
end
def showAllDatasetsCurrency()
set chart to Chart.getChart('currencyBalanceChart')
for dataset in chart.data.datasets
call chart.setDatasetVisibility(chart.data.datasets.indexOf(dataset), true)
for dataset in currencyChart.data.datasets
call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), true)
end
call chart.update()
call currencyChart.update()
end
</script>

View File

@@ -1,9 +1,6 @@
services:
web: &django
build:
context: .
dockerfile: ./docker/prod/django/Dockerfile
image: ${SERVER_NAME}
image: eitchtee/wygiwyh:latest
container_name: ${SERVER_NAME}
command: /start
ports:
@@ -27,7 +24,6 @@ services:
procrastinate:
<<: *django
image: ${PROCRASTINATE_NAME}
container_name: ${PROCRASTINATE_NAME}
depends_on:
- db

View File

@@ -6,16 +6,16 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
&& rm -rf /var/lib/apt/lists/*
COPY ./requirements.txt .
RUN pip wheel --wheel-dir /usr/src/app/wheels -r requirements.txt
RUN --mount=type=cache,target=/root/.cache/pip \
pip wheel --wheel-dir /usr/src/app/wheels -r requirements.txt
FROM node:lts-alpine AS webpack_build
WORKDIR /usr/src/frontend
COPY ./frontend .
COPY ./app/templates /usr/src/app/templates
RUN npm config set registry https://registry.npmmirror.com/ && \
RUN --mount=type=cache,target=/root/.npm \
npm install --verbose && \
npm run build && \
npm cache clean --force
npm run build
FROM python:3.11-slim-buster AS python-run-stage
COPY --from=webpack_build /usr/src/frontend/build /usr/src/frontend/build
@@ -29,12 +29,13 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
COPY --from=python-build-stage /usr/src/app/wheels /wheels/
RUN apt-get update && \
RUN --mount=type=cache,target=/root/.cache/apt \
apt-get update && \
apt-get install --no-install-recommends -y gettext && \
rm -rf /var/lib/apt/lists/* && \
pip install --upgrade pip && \
pip install --no-cache-dir --no-index --find-links=/wheels/ /wheels/* && \
rm -rf /wheels/ ~/.cache/pip/*
rm -rf /wheels/
COPY --chown=app:app ./docker/prod/django/start /start
COPY --chown=app:app ./docker/prod/procrastinate/start /start-procrastinate

View File

@@ -3441,9 +3441,9 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
},
"node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"engines": {
"node": ">= 0.6"
}
@@ -4499,16 +4499,16 @@
}
},
"node_modules/express": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
"integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
"version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@@ -4522,7 +4522,7 @@
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.10",
"path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7",
"qs": "6.13.0",
"range-parser": "~1.2.1",
@@ -4537,6 +4537,10 @@
},
"engines": {
"node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/express/node_modules/debug": {
@@ -5219,9 +5223,9 @@
}
},
"node_modules/http-proxy-middleware": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz",
"integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==",
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz",
"integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==",
"dependencies": {
"@types/http-proxy": "^1.17.8",
"http-proxy": "^1.18.1",
@@ -6179,9 +6183,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"funding": [
{
"type": "github",
@@ -6535,9 +6539,9 @@
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
},
"node_modules/path-to-regexp": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
},
"node_modules/path-type": {
"version": "4.0.0",