Modificación de gráficos #43
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.10 on 2026-10-14 16:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('expenses', '0017_goal_linked_expenses_goal_linked_incomes_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='category',
|
||||||
|
name='expense_type',
|
||||||
|
field=models.CharField(blank=True, choices=[('fixed_essential', 'Fijo esencial'), ('fixed_discretionary', 'Fijo prescindible'), ('variable_essential', 'Variable esencial'), ('variable_discretionary', 'Variable prescindible')], help_text='Si lo dejas vacío, hereda el de la categoría padre.', max_length=30, verbose_name='Tipo de gasto'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -10,6 +10,22 @@ from django.db.models.functions import ExtractMonth, Lower
|
|||||||
|
|
||||||
|
|
||||||
class Category(models.Model):
|
class Category(models.Model):
|
||||||
|
EXPENSE_TYPE_FIXED_ESSENTIAL = "fixed_essential"
|
||||||
|
EXPENSE_TYPE_FIXED_DISCRETIONARY = "fixed_discretionary"
|
||||||
|
EXPENSE_TYPE_VARIABLE_ESSENTIAL = "variable_essential"
|
||||||
|
EXPENSE_TYPE_VARIABLE_DISCRETIONARY = "variable_discretionary"
|
||||||
|
|
||||||
|
EXPENSE_TYPE_CHOICES = [
|
||||||
|
(EXPENSE_TYPE_FIXED_ESSENTIAL, "Fijo esencial"),
|
||||||
|
(EXPENSE_TYPE_FIXED_DISCRETIONARY, "Fijo prescindible"),
|
||||||
|
(EXPENSE_TYPE_VARIABLE_ESSENTIAL, "Variable esencial"),
|
||||||
|
(EXPENSE_TYPE_VARIABLE_DISCRETIONARY, "Variable prescindible"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Saltos máximos al heredar el tipo de gasto. Los ciclos los impide
|
||||||
|
# CategoryForm, pero uno colado por shell o migración no debe colgar nada.
|
||||||
|
EXPENSE_TYPE_MAX_DEPTH = 10
|
||||||
|
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
slug = models.SlugField(blank=True)
|
slug = models.SlugField(blank=True)
|
||||||
|
|
||||||
@ -32,6 +48,14 @@ class Category(models.Model):
|
|||||||
help_text="Categoría creada por la app (traspasos, comisiones). No borrable.",
|
help_text="Categoría creada por la app (traspasos, comisiones). No borrable.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
expense_type = models.CharField(
|
||||||
|
max_length=30,
|
||||||
|
choices=EXPENSE_TYPE_CHOICES,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="Tipo de gasto",
|
||||||
|
help_text="Si lo dejas vacío, hereda el de la categoría padre.",
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ("name", "parent", "owner", "slug")
|
unique_together = ("name", "parent", "owner", "slug")
|
||||||
verbose_name_plural = "categories"
|
verbose_name_plural = "categories"
|
||||||
@ -63,6 +87,22 @@ class Category(models.Model):
|
|||||||
|
|
||||||
return list(seen) if include_self else [i for i in seen if i != self.pk]
|
return list(seen) if include_self else [i for i in seen if i != self.pk]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def effective_expense_type(self):
|
||||||
|
"""Tipo de gasto propio o, si está vacío, el del ancestro más cercano
|
||||||
|
que lo tenga. Cadena vacía si nadie en la cadena lo tiene.
|
||||||
|
|
||||||
|
Cada salto lee self.parent: no lo uses por fila en un listado (N+1).
|
||||||
|
El listado de categorías lo resuelve en _category_tree."""
|
||||||
|
category = self
|
||||||
|
for _ in range(self.EXPENSE_TYPE_MAX_DEPTH):
|
||||||
|
if category is None:
|
||||||
|
return ""
|
||||||
|
if category.expense_type:
|
||||||
|
return category.expense_type
|
||||||
|
category = category.parent
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
class Account(models.Model):
|
class Account(models.Model):
|
||||||
owner = models.ForeignKey(
|
owner = models.ForeignKey(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user