Initial commit: EFINTEL ERP Django Port
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
import uuid
|
||||
|
||||
# Enums
|
||||
class CurrencyEnum(models.TextChoices):
|
||||
CLP = 'CLP', 'Pesos Chilenos'
|
||||
USD = 'USD', 'Dólares Estadounidenses'
|
||||
|
||||
class ExpenseTypeEnum(models.TextChoices):
|
||||
COSTO_DIRECTO = 'COSTO_DIRECTO_PROYECTO', 'Costo Directo Proyecto'
|
||||
GASTO_OPERACIONAL = 'GASTO_OPERACIONAL_OVERHEAD', 'Gasto Operacional Overhead'
|
||||
|
||||
class ApprovalStatusEnum(models.TextChoices):
|
||||
EN_REVISION = 'EN_REVISION_DOCUMENTAL', 'En Revisión Documental (Paso 1)'
|
||||
SOLICITADO_FINANZAS = 'SOLICITADO_FINANZAS', 'Solicitado a Finanzas (Paso 2)'
|
||||
LIBERADO_PAGADO = 'LIBERADO_PAGADO', 'Pago Liberado (Paso 3)'
|
||||
OBSERVADO_RECHAZADO = 'OBSERVADO_RECHAZADO', 'Observado / Rechazado'
|
||||
|
||||
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')
|
||||
name = models.CharField(max_length=200, verbose_name="Nombre del Proyecto/Centro")
|
||||
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")
|
||||
|
||||
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")
|
||||
|
||||
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")
|
||||
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_projects', verbose_name="Creado por")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.code} - {self.name}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Proyecto / Centro de Costo"
|
||||
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')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.code}: {self.name}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Categoría"
|
||||
verbose_name_plural = "Categorías"
|
||||
|
||||
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")
|
||||
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, related_name='expenses', verbose_name="Subcategoría")
|
||||
|
||||
# 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)")
|
||||
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")
|
||||
|
||||
# Geographic & Logistic fields (Operaciones en terreno)
|
||||
region = models.CharField(max_length=100, blank=True, null=True, verbose_name="Región (Chile)")
|
||||
comuna = models.CharField(max_length=100, blank=True, null=True, verbose_name="Comuna")
|
||||
vehicle_ownership = models.CharField(max_length=100, blank=True, null=True, verbose_name="Modalidad / Vehículo")
|
||||
|
||||
# 3-step Approval Flow Tracking
|
||||
approval_status = models.CharField(max_length=50, choices=ApprovalStatusEnum.choices, default=ApprovalStatusEnum.EN_REVISION, verbose_name="Estado de Aprobación")
|
||||
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='expenses_created', verbose_name="Registrado por")
|
||||
responsible_person = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='expenses_responsible', verbose_name="Responsable Solicitante")
|
||||
elevated_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='expenses_elevated', verbose_name="Elevado por (Paso 2)")
|
||||
paid_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='expenses_paid', verbose_name="Pagado por (Paso 3)")
|
||||
|
||||
rejection_reason = models.TextField(blank=True, null=True, verbose_name="Motivo de Observación/Rechazo")
|
||||
paid_at = models.DateTimeField(null=True, blank=True, verbose_name="Fecha de Pago")
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.expense_code} - {self.amount_clp} CLP ({self.date})"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Gasto"
|
||||
verbose_name_plural = "Gastos"
|
||||
ordering = ['-date', '-created_at']
|
||||
|
||||
class Document(models.Model):
|
||||
expense = models.ForeignKey(Expense, on_delete=models.CASCADE, related_name='documents', verbose_name="Gasto Relacionado")
|
||||
file = models.FileField(upload_to='evidences/', verbose_name="Archivo Adjunto")
|
||||
file_type = models.CharField(max_length=50, blank=True, verbose_name="Tipo de Archivo")
|
||||
title = models.CharField(max_length=200, blank=True, verbose_name="Título del Documento")
|
||||
uploaded_at = models.DateTimeField(auto_now_add=True, verbose_name="Fecha de Subida")
|
||||
uploaded_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, verbose_name="Subido por")
|
||||
|
||||
def __str__(self):
|
||||
return f"Evidencia para {self.expense.expense_code}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Documento Evidencia"
|
||||
verbose_name_plural = "Documentos Evidencias"
|
||||
|
||||
class UserProfile(models.Model):
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile', verbose_name="Usuario")
|
||||
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")
|
||||
|
||||
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")
|
||||
|
||||
def __str__(self):
|
||||
return f"Perfil de {self.user.username}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Perfil de Usuario"
|
||||
verbose_name_plural = "Perfiles de Usuario"
|
||||
|
||||
class AuditLog(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Usuario")
|
||||
action = models.CharField(max_length=100, verbose_name="Acción")
|
||||
entity_name = models.CharField(max_length=100, blank=True, null=True, verbose_name="Entidad (Tabla)")
|
||||
entity_id = models.CharField(max_length=100, blank=True, null=True, verbose_name="ID Entidad")
|
||||
field_changed = models.CharField(max_length=100, blank=True, null=True, verbose_name="Campo Modificado")
|
||||
old_value = models.TextField(blank=True, null=True, verbose_name="Valor Anterior")
|
||||
new_value = models.TextField(blank=True, null=True, verbose_name="Valor Nuevo")
|
||||
|
||||
description = models.TextField(verbose_name="Detalle del Cambio")
|
||||
timestamp = models.DateTimeField(auto_now_add=True, verbose_name="Fecha y Hora")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.timestamp} - {self.user.username if self.user else 'Sistema'}: {self.action}"
|
||||
|
||||
class Meta:
|
||||
ordering = ['-timestamp']
|
||||
verbose_name = "Log de Auditoría (SAP CDHDR/CDPOS)"
|
||||
verbose_name_plural = "Logs de Auditoría"
|
||||
|
||||
class SystemSetting(models.Model):
|
||||
global_budget = models.DecimalField(max_digits=15, decimal_places=2, default=25000000.00, verbose_name="Presupuesto General I+D+i")
|
||||
usd_exchange_rate = models.DecimalField(max_digits=10, decimal_places=2, default=950.0, verbose_name="Tasa USD/CLP por Defecto")
|
||||
|
||||
def __str__(self):
|
||||
return "Configuración Global EFINTEL"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Configuración del Sistema"
|
||||
verbose_name_plural = "Configuraciones del Sistema"
|
||||
Reference in New Issue
Block a user