Feat: dashboard con gráficos, estructura CBS/Portafolios, semáforo de proyectos y cartera de especialistas
This commit is contained in:
+45
-5
@@ -7,6 +7,11 @@ class CurrencyEnum(models.TextChoices):
|
||||
CLP = 'CLP', 'Pesos Chilenos'
|
||||
USD = 'USD', 'Dólares Estadounidenses'
|
||||
|
||||
class TaxTypeEnum(models.TextChoices):
|
||||
EXENTO = 'EXENTO', 'Exento / No Afecto'
|
||||
IVA_19 = 'IVA_19', 'Afecto a IVA (19%)'
|
||||
HONORARIOS = 'HONORARIOS_13_75', 'Boleta de Honorarios (13.75%)'
|
||||
|
||||
class ExpenseTypeEnum(models.TextChoices):
|
||||
COSTO_DIRECTO = 'COSTO_DIRECTO_PROYECTO', 'Costo Directo Proyecto'
|
||||
GASTO_OPERACIONAL = 'GASTO_OPERACIONAL_OVERHEAD', 'Gasto Operacional Overhead'
|
||||
@@ -17,6 +22,11 @@ class ApprovalStatusEnum(models.TextChoices):
|
||||
LIBERADO_PAGADO = 'LIBERADO_PAGADO', 'Pago Liberado (Paso 3)'
|
||||
OBSERVADO_RECHAZADO = 'OBSERVADO_RECHAZADO', 'Observado / Rechazado'
|
||||
|
||||
class ProjectStatusEnum(models.TextChoices):
|
||||
ACTIVO = 'ACTIVO', 'Activo'
|
||||
PAUSADA = 'PAUSADA', 'En Pausa'
|
||||
TERMINADO = 'TERMINADO', 'Terminado'
|
||||
|
||||
class Project(models.Model):
|
||||
# Added core project/costcenter fields mapped from Prisma
|
||||
code = models.CharField(max_length=50, unique=True, verbose_name="Código", default='PROY-000')
|
||||
@@ -24,13 +34,15 @@ class Project(models.Model):
|
||||
description = models.TextField(blank=True, verbose_name="Descripción")
|
||||
|
||||
manager = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='managed_projects', verbose_name="Encargado")
|
||||
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children', verbose_name="Portafolio / Proyecto Padre")
|
||||
|
||||
assigned_budget_clp = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name="Presupuesto Asignado (CLP)")
|
||||
assigned_budget_usd = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name="Presupuesto Asignado (USD)")
|
||||
actual_cost_clp = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name="Costo Real Acumulado (CLP)")
|
||||
|
||||
type = models.CharField(max_length=50, choices=ExpenseTypeEnum.choices, default=ExpenseTypeEnum.COSTO_DIRECTO, verbose_name="Tipo de Centro")
|
||||
status = models.CharField(max_length=50, default="on-budget", verbose_name="Estado de Presupuesto")
|
||||
status = models.CharField(max_length=50, choices=ProjectStatusEnum.choices, default=ProjectStatusEnum.ACTIVO, verbose_name="Estado de Proyecto")
|
||||
category = models.ForeignKey('Category', on_delete=models.SET_NULL, null=True, blank=True, related_name='projects', verbose_name="Categoría del Proyecto")
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Fecha de Creación")
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name="Última Modificación")
|
||||
@@ -44,11 +56,10 @@ class Project(models.Model):
|
||||
verbose_name_plural = "Proyectos y Centros"
|
||||
|
||||
class Category(models.Model):
|
||||
# Category / Subcategory hierarchy
|
||||
code = models.CharField(max_length=10, unique=True, verbose_name="Código de Categoría")
|
||||
name = models.CharField(max_length=100, verbose_name="Nombre de Categoría")
|
||||
description = models.TextField(blank=True, verbose_name="Descripción")
|
||||
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='subcategories')
|
||||
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children', verbose_name="Categoría Padre")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.code}: {self.name}"
|
||||
@@ -57,6 +68,30 @@ class Category(models.Model):
|
||||
verbose_name = "Categoría"
|
||||
verbose_name_plural = "Categorías"
|
||||
|
||||
class Vendor(models.Model):
|
||||
rut = models.CharField(max_length=15, unique=True, verbose_name="RUT Proveedor")
|
||||
name = models.CharField(max_length=200, verbose_name="Razón Social / Nombre")
|
||||
business_line = models.CharField(max_length=200, blank=True, null=True, verbose_name="Giro Comercial")
|
||||
|
||||
# Banking details
|
||||
bank = models.CharField(max_length=100, blank=True, null=True, verbose_name="Banco")
|
||||
account_type = models.CharField(max_length=50, blank=True, null=True, verbose_name="Tipo de Cuenta")
|
||||
account_number = models.CharField(max_length=50, blank=True, null=True, verbose_name="Número de Cuenta")
|
||||
email = models.EmailField(blank=True, null=True, verbose_name="Correo de Pago/Facturación")
|
||||
|
||||
payment_terms = models.IntegerField(default=30, verbose_name="Plazo de Pago (Días)")
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.rut} - {self.name}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Proveedor"
|
||||
verbose_name_plural = "Proveedores"
|
||||
ordering = ['name']
|
||||
|
||||
class Expense(models.Model):
|
||||
expense_code = models.CharField(max_length=50, unique=True, default=uuid.uuid4, verbose_name="Código Transacción")
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='expenses', verbose_name="Proyecto")
|
||||
@@ -64,14 +99,18 @@ class Expense(models.Model):
|
||||
|
||||
# Financial fields
|
||||
amount_clp = models.DecimalField(max_digits=15, decimal_places=2, verbose_name="Monto Neto (CLP)")
|
||||
amount_usd = models.DecimalField(max_digits=15, decimal_places=2, verbose_name="Monto Neto (USD)")
|
||||
tax_type = models.CharField(max_length=20, choices=TaxTypeEnum.choices, default=TaxTypeEnum.IVA_19, verbose_name="Tipo de Impuesto")
|
||||
tax_amount_clp = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name="Monto Impuesto (CLP)")
|
||||
total_amount_clp = models.DecimalField(max_digits=15, decimal_places=2, default=0, verbose_name="Monto Total (CLP)")
|
||||
|
||||
amount_usd = models.DecimalField(max_digits=15, decimal_places=2, verbose_name="Monto Total (USD)")
|
||||
currency = models.CharField(max_length=10, choices=CurrencyEnum.choices, default=CurrencyEnum.CLP, verbose_name="Moneda Original")
|
||||
exchange_rate = models.DecimalField(max_digits=10, decimal_places=2, default=950.0, verbose_name="Tipo de Cambio")
|
||||
|
||||
description = models.TextField(verbose_name="Detalle de Imputación")
|
||||
date = models.DateField(verbose_name="Fecha del Gasto")
|
||||
|
||||
vendor = models.CharField(max_length=200, blank=True, null=True, verbose_name="Proveedor / Emisor")
|
||||
vendor = models.ForeignKey(Vendor, on_delete=models.SET_NULL, null=True, blank=True, related_name='expenses', verbose_name="Proveedor / Emisor")
|
||||
|
||||
# Geographic & Logistic fields (Operaciones en terreno)
|
||||
region = models.CharField(max_length=100, blank=True, null=True, verbose_name="Región (Chile)")
|
||||
@@ -120,6 +159,7 @@ class UserProfile(models.Model):
|
||||
rut = models.CharField(max_length=20, blank=True, null=True, verbose_name="RUT")
|
||||
job_title = models.CharField(max_length=150, blank=True, null=True, verbose_name="Cargo")
|
||||
description = models.TextField(blank=True, verbose_name="Descripción del Responsable")
|
||||
specialties = models.ManyToManyField('Category', blank=True, related_name='specialists', verbose_name="Especialidades / Habilidades")
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Fecha de Registro")
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name="Última Modificación")
|
||||
|
||||
Reference in New Issue
Block a user