import re with open('expenses/models.py', 'r', encoding='utf-8') as f: content = f.read() # Add TimeStampedModel and CatalogModel right before Project abstracts = """ from django.core.exceptions import ValidationError class TimeStampedModel(models.Model): 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") class Meta: abstract = True class CatalogModel(TimeStampedModel): code = models.CharField(max_length=50, unique=True, verbose_name="Código") name = models.CharField(max_length=200, verbose_name="Nombre") description = models.TextField(blank=True, verbose_name="Descripción") class Meta: abstract = True """ if 'class TimeStampedModel' not in content: content = content.replace("class Project(models.Model):", abstracts + "class Project(CatalogModel):") # Clean Project content = content.replace(" code = models.CharField(max_length=50, unique=True, verbose_name=\"Código\", default='PROY-000')\n", "") content = content.replace(" name = models.CharField(max_length=200, verbose_name=\"Nombre del Proyecto/Centro\")\n", "") content = content.replace(" description = models.TextField(blank=True, verbose_name=\"Descripción\")\n", "") content = content.replace(" created_at = models.DateTimeField(auto_now_add=True, verbose_name=\"Fecha de Creación\")\n", "") content = content.replace(" updated_at = models.DateTimeField(auto_now=True, verbose_name=\"Última Modificación\")\n", "") # Clean Category content = content.replace("class Category(models.Model):", "class Category(CatalogModel):") content = content.replace(" code = models.CharField(max_length=10, unique=True, verbose_name=\"Código de Categoría\")\n", "") content = content.replace(" name = models.CharField(max_length=100, verbose_name=\"Nombre de Categoría\")\n", "") content = content.replace(" description = models.TextField(blank=True, verbose_name=\"Descripción\")\n", "") # Clean Vendor content = content.replace("class Vendor(models.Model):", "class Vendor(TimeStampedModel):") content = content.replace(" created_at = models.DateTimeField(auto_now_add=True)\n updated_at = models.DateTimeField(auto_now=True)\n", "") # Clean Expense content = content.replace("class Expense(models.Model):", "class Expense(TimeStampedModel):") # We must only replace the first occurrence of these dates inside Expense, but replace is global. # So we do a regex to target Expense block. # Actually Expense is the only one with exactly these lines together at the end. content = re.sub(r' created_at = models.DateTimeField\(auto_now_add=True\)\n updated_at = models.DateTimeField\(auto_now=True\)\n\n def save', ' def save', content) encap = """ def clean(self): super().clean() if self.amount_clp and self.amount_clp < 0: raise ValidationError({'amount_clp': 'El monto neto no puede ser negativo.'}) amount = Decimal(str(self.amount_clp or '0')) tax = Decimal(str(self.tax_amount_clp or '0')) expected = amount + tax if self.total_amount_clp and abs(self.total_amount_clp - expected) > Decimal('1.0'): raise ValidationError({'total_amount_clp': f'Discrepancia. Total esperado: {expected}'}) def approve_expense(self, user): if self.approval_status == ApprovalStatusEnum.EN_REVISION: self.approval_status = ApprovalStatusEnum.SOLICITADO_FINANZAS self.elevated_by = user self.save() def save(self, *args, **kwargs):""" content = content.replace(" def save(self, *args, **kwargs):", encap) proxy = """ class Reimbursement(Expense): class Meta: proxy = True verbose_name = "Rendición Especializada" verbose_name_plural = "Rendiciones Especializadas" def get_approval_flow(self): return "Flujo Acelerado de Reembolso" """ if 'class Reimbursement' not in content: content += proxy with open('expenses/models.py', 'w', encoding='utf-8') as f: f.write(content)