Compare commits

..

6 Commits

Author SHA1 Message Date
Herculino Trotta
dcab83f936 Merge pull request #182
fix(insights:category-explorer): wrong sums
2025-02-19 16:02:14 -03:00
Herculino Trotta
b228e4ec26 fix(insights:category-explorer): wrong sums 2025-02-19 16:01:53 -03:00
Herculino Trotta
4071a1301f Merge pull request #181 from eitchtee/dev
fix(export): unable to import decimals
2025-02-19 15:44:50 -03:00
Herculino Trotta
5c9db10710 fix(export): unable to import decimals 2025-02-19 15:44:18 -03:00
Herculino Trotta
19c92e0014 Merge pull request #180
fix(export): 403 when exporting
2025-02-19 14:02:52 -03:00
Herculino Trotta
6459f2eb46 fix(export): 403 when exporting 2025-02-19 14:02:31 -03:00
9 changed files with 102 additions and 45 deletions

View File

@@ -2,13 +2,15 @@ from import_export import fields, resources, widgets
from apps.accounts.models import Account
from apps.currencies.models import Currency, ExchangeRate, ExchangeRateService
from apps.export_app.widgets.foreign_key import SkipMissingForeignKeyWidget
from apps.export_app.widgets.numbers import UniversalDecimalWidget
class CurrencyResource(resources.ModelResource):
exchange_currency = fields.Field(
attribute="exchange_currency",
column_name="exchange_currency",
widget=widgets.ForeignKeyWidget(Currency, "name"),
widget=SkipMissingForeignKeyWidget(Currency, "name"),
)
class Meta:
@@ -26,6 +28,9 @@ class ExchangeRateResource(resources.ModelResource):
column_name="to_currency",
widget=widgets.ForeignKeyWidget(Currency, "name"),
)
rate = fields.Field(
attribute="rate", column_name="rate", widget=UniversalDecimalWidget()
)
class Meta:
model = ExchangeRate

View File

@@ -3,6 +3,7 @@ from import_export.widgets import ForeignKeyWidget
from apps.dca.models import DCAStrategy, DCAEntry
from apps.currencies.models import Currency
from apps.export_app.widgets.numbers import UniversalDecimalWidget
class DCAStrategyResource(resources.ModelResource):
@@ -22,5 +23,16 @@ class DCAStrategyResource(resources.ModelResource):
class DCAEntryResource(resources.ModelResource):
amount_paid = fields.Field(
attribute="amount_paid",
column_name="amount_paid",
widget=UniversalDecimalWidget(),
)
amount_received = fields.Field(
attribute="amount_received",
column_name="amount_received",
widget=UniversalDecimalWidget(),
)
class Meta:
model = DCAEntry

View File

@@ -13,6 +13,7 @@ from apps.transactions.models import (
RecurringTransaction,
InstallmentPlan,
)
from apps.export_app.widgets.numbers import UniversalDecimalWidget
class TransactionResource(resources.ModelResource):
@@ -44,6 +45,12 @@ class TransactionResource(resources.ModelResource):
column_name="internal_id", attribute="internal_id"
)
amount = fields.Field(
attribute="amount",
column_name="amount",
widget=UniversalDecimalWidget(),
)
class Meta:
model = Transaction
@@ -91,6 +98,12 @@ class RecurringTransactionResource(resources.ModelResource):
widget=AutoCreateManyToManyWidget(TransactionEntity, field="name"),
)
amount = fields.Field(
attribute="amount",
column_name="amount",
widget=UniversalDecimalWidget(),
)
class Meta:
model = RecurringTransaction
@@ -120,5 +133,11 @@ class InstallmentPlanResource(resources.ModelResource):
widget=AutoCreateManyToManyWidget(TransactionEntity, field="name"),
)
installment_amount = fields.Field(
attribute="installment_amount",
column_name="installment_amount",
widget=UniversalDecimalWidget(),
)
class Meta:
model = InstallmentPlan

View File

@@ -50,7 +50,6 @@ def export_index(request):
return render(request, "export_app/pages/index.html")
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
def export_form(request):
@@ -241,11 +240,6 @@ def process_imports(request, cleaned_data):
dataset = Dataset()
dataset.load(content, format="csv")
# Debug logging
logger.debug(f"Importing {field_name}")
logger.debug(f"Headers: {dataset.headers}")
logger.debug(f"First row: {dataset[0] if len(dataset) > 0 else 'No data'}")
# Perform the import
result = resource.import_data(
dataset,
@@ -266,6 +260,8 @@ def process_imports(request, cleaned_data):
raise ImportError(f"Error importing {field_name}: {str(e)}")
with transaction.atomic():
files = {}
if zip_file := cleaned_data.get("zip_file"):
# Process ZIP file
with zipfile.ZipFile(zip_file) as z:
@@ -274,10 +270,12 @@ def process_imports(request, cleaned_data):
with z.open(filename) as f:
content = f.read().decode("utf-8")
for field_name, resource_class in import_order:
if name == field_name:
import_dataset(content, resource_class, field_name)
break
files[name] = content
for field_name, resource_class in import_order:
if field_name in files.keys():
content = files[field_name]
import_dataset(content, resource_class, field_name)
else:
# Process individual files
for field_name, resource_class in import_order:

View File

@@ -9,3 +9,14 @@ class AutoCreateForeignKeyWidget(ForeignKeyWidget):
except self.model.DoesNotExist:
return self.model.objects.create(name=value)
return None
class SkipMissingForeignKeyWidget(ForeignKeyWidget):
def clean(self, value, row=None, *args, **kwargs):
if not value:
return None
try:
return super().clean(value, row, *args, **kwargs)
except self.model.DoesNotExist:
return None

View File

@@ -0,0 +1,18 @@
from decimal import Decimal
from import_export.widgets import NumberWidget
class UniversalDecimalWidget(NumberWidget):
def clean(self, value, row=None, *args, **kwargs):
if self.is_empty(value):
return None
# Replace comma with dot if present
if isinstance(value, str):
value = value.replace(",", ".")
return Decimal(str(value))
def render(self, value, obj=None, **kwargs):
if value is None:
return ""
return str(value).replace(",", ".")

View File

@@ -14,8 +14,7 @@ def get_category_sums_by_account(queryset, category=None):
current_income=Coalesce(
Sum(
Case(
When(type="IN", then="amount"),
When(is_paid=True, then="amount"),
When(type="IN", is_paid=True, then="amount"),
default=Value(0),
output_field=DecimalField(max_digits=42, decimal_places=30),
)
@@ -26,8 +25,7 @@ def get_category_sums_by_account(queryset, category=None):
current_expense=Coalesce(
Sum(
Case(
When(type="EX", then=-F("amount")),
When(is_paid=True, then="amount"),
When(type="EX", is_paid=True, then=-F("amount")),
default=Value(0),
output_field=DecimalField(max_digits=42, decimal_places=30),
)
@@ -38,8 +36,7 @@ def get_category_sums_by_account(queryset, category=None):
projected_income=Coalesce(
Sum(
Case(
When(type="IN", then="amount"),
When(is_paid=False, then="amount"),
When(type="IN", is_paid=False, then="amount"),
default=Value(0),
output_field=DecimalField(max_digits=42, decimal_places=30),
)
@@ -50,8 +47,7 @@ def get_category_sums_by_account(queryset, category=None):
projected_expense=Coalesce(
Sum(
Case(
When(type="EX", then=-F("amount")),
When(is_paid=False, then="amount"),
When(type="EX", is_paid=False, then=-F("amount")),
default=Value(0),
output_field=DecimalField(max_digits=42, decimal_places=30),
)
@@ -97,8 +93,7 @@ def get_category_sums_by_currency(queryset, category=None):
current_income=Coalesce(
Sum(
Case(
When(type="IN", then="amount"),
When(is_paid=True, then="amount"),
When(type="IN", is_paid=True, then="amount"),
default=Value(0),
output_field=DecimalField(max_digits=42, decimal_places=30),
)
@@ -109,8 +104,7 @@ def get_category_sums_by_currency(queryset, category=None):
current_expense=Coalesce(
Sum(
Case(
When(type="EX", then=-F("amount")),
When(is_paid=True, then="amount"),
When(type="EX", is_paid=True, then=-F("amount")),
default=Value(0),
output_field=DecimalField(max_digits=42, decimal_places=30),
)
@@ -121,8 +115,7 @@ def get_category_sums_by_currency(queryset, category=None):
projected_income=Coalesce(
Sum(
Case(
When(type="IN", then="amount"),
When(is_paid=False, then="amount"),
When(type="IN", is_paid=False, then="amount"),
default=Value(0),
output_field=DecimalField(max_digits=42, decimal_places=30),
)
@@ -133,8 +126,7 @@ def get_category_sums_by_currency(queryset, category=None):
projected_expense=Coalesce(
Sum(
Case(
When(type="EX", then=-F("amount")),
When(is_paid=False, then="amount"),
When(type="EX", is_paid=False, then=-F("amount")),
default=Value(0),
output_field=DecimalField(max_digits=42, decimal_places=30),
)

View File

@@ -57,9 +57,9 @@
labels: accountData.labels,
datasets: [
{
label: "{% trans 'Current Income' %}",
data: accountData.datasets[0].data,
backgroundColor: '#4dde80',
label: "{% trans 'Projected Expenses' %}",
data: accountData.datasets[3].data,
backgroundColor: '#f8717180', // Added transparency
stack: 'stack0'
},
{
@@ -68,18 +68,19 @@
backgroundColor: '#f87171',
stack: 'stack0'
},
{
label: "{% trans 'Current Income' %}",
data: accountData.datasets[0].data,
backgroundColor: '#4dde80',
stack: 'stack0'
},
{
label: "{% trans 'Projected Income' %}",
data: accountData.datasets[2].data,
backgroundColor: '#4dde8080', // Added transparency
stack: 'stack0'
},
{
label: "{% trans 'Projected Expenses' %}",
data: accountData.datasets[3].data,
backgroundColor: '#f8717180', // Added transparency
stack: 'stack0'
}
]
},
options: {

View File

@@ -57,9 +57,9 @@
labels: currencyData.labels,
datasets: [
{
label: "{% trans 'Current Income' %}",
data: currencyData.datasets[0].data,
backgroundColor: '#4dde80',
label: "{% trans 'Projected Expenses' %}",
data: currencyData.datasets[3].data,
backgroundColor: '#f8717180', // Added transparency
stack: 'stack0'
},
{
@@ -68,18 +68,19 @@
backgroundColor: '#f87171',
stack: 'stack0'
},
{
label: "{% trans 'Current Income' %}",
data: currencyData.datasets[0].data,
backgroundColor: '#4dde80',
stack: 'stack0'
},
{
label: "{% trans 'Projected Income' %}",
data: currencyData.datasets[2].data,
backgroundColor: '#4dde8080', // Added transparency
stack: 'stack0'
},
{
label: "{% trans 'Projected Expenses' %}",
data: currencyData.datasets[3].data,
backgroundColor: '#f8717180', // Added transparency
stack: 'stack0'
}
]
},
options: chartOptions