{"id":83068,"date":"2024-11-09T03:51:03","date_gmt":"2024-11-09T00:21:03","guid":{"rendered":"https:\/\/nabfollower.com\/blog\/contabilidad-para-django-developers-periodos-contables-1c30\/"},"modified":"2024-11-09T03:51:03","modified_gmt":"2024-11-09T00:21:03","slug":"contabilidad-para-django-developers-periodos-contables-1c30","status":"publish","type":"post","link":"https:\/\/nabfollower.com\/blog\/contabilidad-para-django-developers-periodos-contables-1c30\/","title":{"rendered":"\u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc \u0628\u0631\u0627\u06cc \u062a\u0648\u0633\u0639\u0647 \u062f\u0647\u0646\u062f\u06af\u0627\u0646 \u062c\u0646\u06af\u0648: \u062f\u0648\u0631\u0647 \u0647\u0627\u06cc \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc"},"content":{"rendered":"<p>Summarize this content to 400 words in Persian Lang <\/p>\n<p>  \u0645\u0642\u062f\u0645\u0647<\/p>\n<p>\u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u06cc\u06a9 \u062a\u0648\u0633\u0639\u0647 \u062f\u0647\u0646\u062f\u0647 \u062c\u0646\u06af\u0648\u060c \u0627\u062d\u062a\u0645\u0627\u0644\u0627\u064b \u0628\u0627 \u0633\u06cc\u0633\u062a\u0645 \u0647\u0627\u06cc\u06cc \u06a9\u0627\u0631 \u06a9\u0631\u062f\u0647 \u0627\u06cc\u062f \u06a9\u0647 \u062f\u0627\u062f\u0647 \u0647\u0627\u06cc \u0632\u0645\u0627\u0646\u06cc \u0631\u0627 \u0645\u062f\u06cc\u0631\u06cc\u062a \u0645\u06cc \u06a9\u0646\u0646\u062f\u060c \u0627\u0645\u0627 \u062f\u0648\u0631\u0647 \u0647\u0627\u06cc \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc \u0645\u062a\u0641\u0627\u0648\u062a \u0627\u0633\u062a. \u0633\u06cc\u0633\u062a\u0645\u06cc \u0631\u0627 \u062a\u0635\u0648\u0631 \u06a9\u0646\u06cc\u062f \u06a9\u0647 \u062f\u0631 \u0622\u0646 \u0628\u0627\u06cc\u062f \u0627\u0637\u0645\u06cc\u0646\u0627\u0646 \u062d\u0627\u0635\u0644 \u06a9\u0646\u06cc\u062f \u06a9\u0647 \u062a\u0645\u0627\u0645 \u0639\u0645\u0644\u06cc\u0627\u062a \u0645\u0627\u0644\u06cc \u0628\u0647 \u062f\u0631\u0633\u062a\u06cc \u062f\u0631 \u062f\u0648\u0631\u0647 \u0647\u0627\u06cc \u0632\u0645\u0627\u0646\u06cc \u062e\u0627\u0635 \u0633\u0627\u0632\u0645\u0627\u0646\u062f\u0647\u06cc \u0634\u062f\u0647 \u0627\u0646\u062f \u0648 \u0632\u0645\u0627\u0646\u06cc \u06a9\u0647 \u06cc\u06a9 \u062f\u0648\u0631\u0647 \u0628\u0633\u062a\u0647 \u0645\u06cc \u0634\u0648\u062f\u060c \u062f\u0627\u062f\u0647 \u0647\u0627 \u062a\u063a\u06cc\u06cc\u0631 \u0646\u0627\u067e\u0630\u06cc\u0631 \u0647\u0633\u062a\u0646\u062f.<\/p>\n<p>\u0627\u06cc\u0646 \u0622\u0645\u0648\u0632\u0634 \u0634\u0645\u0627 \u0631\u0627 \u062f\u0631 \u067e\u06cc\u0627\u062f\u0647 \u0633\u0627\u0632\u06cc \u06cc\u06a9 \u0633\u06cc\u0633\u062a\u0645 \u062f\u0648\u0631\u0647 \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc \u0642\u0648\u06cc \u06a9\u0647 \u06cc\u06a9\u067e\u0627\u0631\u0686\u06af\u06cc \u062f\u0627\u062f\u0647 \u0647\u0627\u06cc \u0645\u0627\u0644\u06cc \u0631\u0627 \u062a\u0636\u0645\u06cc\u0646 \u0645\u06cc \u06a9\u0646\u062f\u060c \u062a\u0646\u0647\u0627 \u0628\u0627 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0627\u0632 \u0627\u062f\u0645\u06cc\u0646 \u062c\u0646\u06af\u0648 \u0631\u0627\u0647\u0646\u0645\u0627\u06cc\u06cc \u0645\u06cc \u06a9\u0646\u062f. \u0634\u0645\u0627 \u06cc\u0627\u062f \u062e\u0648\u0627\u0647\u06cc\u062f \u06af\u0631\u0641\u062a \u06a9\u0647 \u0628\u0627\u0632\u060c \u0639\u0645\u0644\u06cc\u0627\u062a \u0648 \u0628\u0633\u062a\u0647 \u0634\u062f\u0646 \u062f\u0648\u0631\u0647 \u0647\u0627\u06cc \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc \u0631\u0627 \u0628\u0647 \u0647\u0645\u0631\u0627\u0647 \u062a\u0645\u0627\u0645 \u0627\u0639\u062a\u0628\u0627\u0631\u0633\u0646\u062c\u06cc \u0647\u0627\u06cc \u0644\u0627\u0632\u0645 \u0628\u0631\u0627\u06cc \u062d\u0641\u0638 \u06cc\u06a9\u067e\u0627\u0631\u0686\u06af\u06cc \u062f\u0627\u062f\u0647 \u0647\u0627 \u0645\u062f\u06cc\u0631\u06cc\u062a \u06a9\u0646\u06cc\u062f.<\/p>\n<p>\u067e\u0633 \u0627\u0632 \u062a\u06a9\u0645\u06cc\u0644\u060c \u0634\u0645\u0627 \u0642\u0627\u062f\u0631 \u062e\u0648\u0627\u0647\u06cc\u062f \u0628\u0648\u062f \u0633\u06cc\u0633\u062a\u0645\u06cc \u0631\u0627 \u067e\u06cc\u0627\u062f\u0647 \u0633\u0627\u0632\u06cc \u06a9\u0646\u06cc\u062f \u06a9\u0647 \u0628\u0647 \u0627\u0635\u0648\u0644 \u0627\u0648\u0644\u06cc\u0647 \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc \u067e\u0627\u06cc\u0628\u0646\u062f \u0628\u0627\u0634\u062f \u0648 \u062f\u0631 \u0639\u06cc\u0646 \u062d\u0627\u0644 \u0628\u0647\u062a\u0631\u06cc\u0646 \u0634\u06cc\u0648\u0647 \u0647\u0627\u06cc \u062a\u0648\u0633\u0639\u0647 \u0631\u0627 \u062f\u0631 \u062c\u0646\u06af\u0648 \u062d\u0641\u0638 \u06a9\u0646\u06cc\u062f.<\/p>\n<p>  \u067e\u06cc\u0634 \u0646\u06cc\u0627\u0632\u0647\u0627<\/p>\n<p>\u067e\u0627\u06cc\u062a\u0648\u0646 3.12<br \/>\n\u062c\u0646\u06af\u0648 5.0<br \/>\n\u062f\u0627\u0646\u0634 \u0627\u0648\u0644\u06cc\u0647 \u0645\u062f\u0644 \u0647\u0627 \u062f\u0631 \u062c\u0646\u06af\u0648<br \/>\nSQLite \u06cc\u0627 PostgreSQL (\u062a\u0648\u0635\u06cc\u0647 \u0645\u06cc \u0634\u0648\u062f)<\/p>\n<p>  \u0645\u0641\u0627\u0647\u06cc\u0645 \u06a9\u0644\u06cc\u062f\u06cc<\/p>\n<p>  \u062f\u0648\u0631\u0647 \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc: \u0645\u0639\u0627\u0645\u0644\u0647 \u0646\u0647\u0627\u06cc\u06cc<\/p>\n<p>\u06cc\u06a9 \u062f\u0648\u0631\u0647 \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc \u0631\u0627 \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u06cc\u06a9 \u0645\u0639\u0627\u0645\u0644\u0647 \u067e\u0627\u06cc\u06af\u0627\u0647 \u062f\u0627\u062f\u0647 \u062f\u0631 \u0645\u0642\u06cc\u0627\u0633 \u0628\u0632\u0631\u06af \u062f\u0631 \u0646\u0638\u0631 \u0628\u06af\u06cc\u0631\u06cc\u062f:<\/p>\n<p>\u0628\u0627\u0632 \u06a9\u0631\u062f\u0646 \u0645\u0627\u0646\u0646\u062f \u0634\u0631\u0648\u0639 \u06cc\u06a9 \u0645\u0639\u0627\u0645\u0644\u0647 \u0627\u0633\u062a (BEGIN TRANSACTION)<br \/>\n\u0639\u0645\u0644\u06cc\u0627\u062a \u0645\u0627\u0646\u0646\u062f \u067e\u0631\u0633 \u0648 \u062c\u0648 \u062f\u0631 \u062a\u0631\u0627\u06a9\u0646\u0634 \u0647\u0633\u062a\u0646\u062f<br \/>\n\u0628\u0633\u062a\u0646 \u0645\u0627\u0646\u0646\u062f \u0627\u062c\u0631\u0627\u06cc \u0622\u0646 \u0627\u0633\u062a COMMIT<\/p>\n<p>  \u0627\u06cc\u0627\u0644\u0627\u062a \u062f\u0648\u0631\u0647<\/p>\n<p>\u0645\u0634\u0627\u0628\u0647 \u062d\u0627\u0644\u062a \u0647\u0627\u06cc \u06cc\u06a9 \u0634\u06cc \u062f\u0631 \u062d\u0627\u0641\u0638\u0647:<\/p>\n<p>DRAFT: \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u06cc\u06a9 \u0634\u06cc\u0621 \u0646\u0645\u0648\u0646\u0647 \u0633\u0627\u0632\u06cc \u0634\u062f\u0647 \u0627\u0645\u0627 \u0630\u062e\u06cc\u0631\u0647 \u0646\u0634\u062f\u0647 \u0627\u0633\u062a<\/p>\n<p>ACTIVE: \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u06cc\u06a9 \u0634\u06cc \u062f\u0631 \u067e\u0627\u06cc\u06af\u0627\u0647 \u062f\u0627\u062f\u0647 \u0628\u0627\u0642\u06cc \u0645\u0627\u0646\u062f<\/p>\n<p>CLOSED: \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u06cc\u06a9 \u0634\u06cc\u0621 \u062a\u063a\u06cc\u06cc\u0631\u0646\u0627\u067e\u0630\u06cc\u0631<\/p>\n<p>  \u067e\u06cc\u0627\u062f\u0647 \u0633\u0627\u0632\u06cc<\/p>\n<p>  \u0645\u062f\u0644 \u0647\u0627\u06cc \u067e\u0627\u06cc\u0647<\/p>\n<p>from django.db import models<br \/>\nfrom django.core.exceptions import ValidationError<br \/>\nfrom django.utils import timezone<br \/>\nfrom decimal import Decimal<br \/>\nfrom django.db.models import Sum<br \/>\nfrom datetime import datetime<\/p>\n<p>class AccountingPeriod(models.Model):<br \/>\n    &#8220;&#8221;&#8221;<br \/>\n    Representa un per\u00edodo contable con su ciclo de vida completo.<br \/>\n    Similar a una transacci\u00f3n de base de datos a gran escala.<br \/>\n    &#8220;&#8221;&#8221;<br \/>\n    STATUS_CHOICES = [<br \/>\n        (&#8216;DRAFT&#8217;, &#8216;Borrador&#8217;),<br \/>\n        (&#8216;ACTIVE&#8217;, &#8216;Activo&#8217;),<br \/>\n        (&#8216;CLOSED&#8217;, &#8216;Cerrado&#8217;),<br \/>\n    ]\n<p>    name = models.CharField(<br \/>\n        max_length=100,<br \/>\n        unique=True,<br \/>\n        help_text=&#8221;Ejemplo: &#8216;Enero 2024&#8242;&#8221;<br \/>\n    )<br \/>\n    start_date = models.DateField()<br \/>\n    end_date = models.DateField()<br \/>\n    status = models.CharField(<br \/>\n        max_length=10,<br \/>\n        choices=STATUS_CHOICES,<br \/>\n        default=&#8217;DRAFT&#8217;<br \/>\n    )<br \/>\n    initial_balance = models.DecimalField(<br \/>\n        max_digits=15,<br \/>\n        decimal_places=2,<br \/>\n        default=Decimal(&#8216;0.00&#8217;)<br \/>\n    )<br \/>\n    created_at = models.DateTimeField(auto_now_add=True)<br \/>\n    closed_at = models.DateTimeField(null=True, blank=True)<br \/>\n    closing_notes = models.TextField(blank=True)<\/p>\n<p>    class Meta:<br \/>\n        ordering = [&#8216;-start_date&#8217;]\n        constraints = [<br \/>\n            models.CheckConstraint(<br \/>\n                check=models.Q(end_date__gte=models.F(&#8216;start_date&#8217;)),<br \/>\n                name=&#8217;valid_period_dates&#8217;<br \/>\n            )<br \/>\n        ]\n<p>    def clean(self):<br \/>\n        self._validate_dates()<br \/>\n        self._validate_status_transition()<br \/>\n        self._validate_overlapping()<\/p>\n<p>    def _validate_dates(self):<br \/>\n        &#8220;&#8221;&#8221;Valida la coherencia de las fechas del per\u00edodo.&#8221;&#8221;&#8221;<br \/>\n        if self.start_date and self.end_date:<br \/>\n            if self.start_date &gt; self.end_date:<br \/>\n                raise ValidationError(<br \/>\n                    &#8216;La fecha de inicio debe ser anterior a la fecha de fin.&#8217;<br \/>\n                )<\/p>\n<p>    def _validate_status_transition(self):<br \/>\n        &#8220;&#8221;&#8221;Valida las transiciones de estado permitidas.&#8221;&#8221;&#8221;<br \/>\n        if not self.pk:<br \/>\n            return<\/p>\n<p>        old_instance = AccountingPeriod.objects.get(pk=self.pk)<br \/>\n        valid_transitions = {<br \/>\n            &#8216;DRAFT&#8217;: [&#8216;ACTIVE&#8217;],<br \/>\n            &#8216;ACTIVE&#8217;: [&#8216;CLOSED&#8217;],<br \/>\n            &#8216;CLOSED&#8217;: []\n        }<\/p>\n<p>        if (self.status != old_instance.status and<br \/>\n            self.status not in valid_transitions[old_instance.status]):<br \/>\n            raise ValidationError(<br \/>\n                f&#8217;Transici\u00f3n de estado inv\u00e1lida: {old_instance.status} -&gt; {self.status}&#8217;<br \/>\n            )<\/p>\n<p>    def _validate_overlapping(self):<br \/>\n        &#8220;&#8221;&#8221;Previene la superposici\u00f3n de per\u00edodos contables.&#8221;&#8221;&#8221;<br \/>\n        overlapping = AccountingPeriod.objects.exclude(pk=self.pk).filter(<br \/>\n            models.Q(start_date__lte=self.end_date) &amp;<br \/>\n            models.Q(end_date__gte=self.start_date)<br \/>\n        )<br \/>\n        if overlapping.exists():<br \/>\n            raise ValidationError(<br \/>\n                &#8216;El per\u00edodo se superpone con per\u00edodos existentes.&#8217;<br \/>\n            )<\/p>\n<p>    def activate(self):<br \/>\n        &#8220;&#8221;&#8221;Activa un per\u00edodo en estado DRAFT.&#8221;&#8221;&#8221;<br \/>\n        if self.status != &#8216;DRAFT&#8217;:<br \/>\n            raise ValidationError(&#8216;Solo se pueden activar per\u00edodos en borrador.&#8217;)<\/p>\n<p>        self.status = &#8216;ACTIVE&#8217;<br \/>\n        self.save()<\/p>\n<p>    def can_close(self):<br \/>\n        &#8220;&#8221;&#8221;<br \/>\n        Verifica si el per\u00edodo puede ser cerrado.<br \/>\n        Retorna (bool, str) donde bool indica si se puede cerrar<br \/>\n        y str contiene el mensaje de error si no se puede.<br \/>\n        &#8220;&#8221;&#8221;<br \/>\n        if self.status != &#8216;ACTIVE&#8217;:<br \/>\n            return False, &#8216;Solo se pueden cerrar per\u00edodos activos.&#8217;<\/p>\n<p>        # Verificar balance<br \/>\n        if not self.is_balanced():<br \/>\n            return False, &#8216;El per\u00edodo no est\u00e1 balanceado.&#8217;<\/p>\n<p>        # Verificar entradas sin procesar<br \/>\n        if self.has_pending_entries():<br \/>\n            return False, &#8216;Existen entradas pendientes de procesar.&#8217;<\/p>\n<p>        return True, &#8221;<\/p>\n<p>    def close(self, closing_notes=&#8221;):<br \/>\n        &#8220;&#8221;&#8221;<br \/>\n        Cierra el per\u00edodo si todas las validaciones pasan.<br \/>\n        &#8220;&#8221;&#8221;<br \/>\n        can_close, message = self.can_close()<br \/>\n        if not can_close:<br \/>\n            raise ValidationError(message)<\/p>\n<p>        self.status = &#8216;CLOSED&#8217;<br \/>\n        self.closed_at = timezone.now()<br \/>\n        self.closing_notes = closing_notes<br \/>\n        self.save()<\/p>\n<p>    def is_balanced(self):<br \/>\n        &#8220;&#8221;&#8221;<br \/>\n        Verifica si los d\u00e9bitos y cr\u00e9ditos est\u00e1n balanceados.<br \/>\n        &#8220;&#8221;&#8221;<br \/>\n        totals = self.entries.aggregate(<br \/>\n            total_debit=Sum(&#8216;debit_amount&#8217;),<br \/>\n            total_credit=Sum(&#8216;credit_amount&#8217;)<br \/>\n        )<\/p>\n<p>        total_debit = totals[&#8216;total_debit&#8217;] or Decimal(&#8216;0.00&#8217;)<br \/>\n        total_credit = totals[&#8216;total_credit&#8217;] or Decimal(&#8216;0.00&#8217;)<\/p>\n<p>        return abs(total_debit &#8211; total_credit) &lt; Decimal(&#8216;0.01&#8242;)<\/p>\n<p>    def has_pending_entries(self):<br \/>\n        &#8220;&#8221;&#8221;<br \/>\n        Verifica si hay entradas pendientes de procesar.<br \/>\n        &#8220;&#8221;&#8221;<br \/>\n        return self.entries.filter(status=&#8217;PENDING&#8217;).exists()<\/p>\n<p>    def __str__(self):<br \/>\n        return f&#8221;{self.name} ({self.get_status_display()})&#8221;<\/p>\n<p>class AccountingEntry(models.Model):<br \/>\n    &#8220;&#8221;&#8221;<br \/>\n    Representa una entrada contable individual dentro de un per\u00edodo.<br \/>\n    &#8220;&#8221;&#8221;<br \/>\n    STATUS_CHOICES = [<br \/>\n        (&#8216;PENDING&#8217;, &#8216;Pendiente&#8217;),<br \/>\n        (&#8216;PROCESSED&#8217;, &#8216;Procesado&#8217;),<br \/>\n        (&#8216;REJECTED&#8217;, &#8216;Rechazado&#8217;),<br \/>\n    ]\n<p>    period = models.ForeignKey(<br \/>\n        AccountingPeriod,<br \/>\n        on_delete=models.PROTECT,<br \/>\n        related_name=&#8217;entries&#8217;<br \/>\n    )<br \/>\n    date = models.DateField()<br \/>\n    description = models.CharField(max_length=200)<br \/>\n    debit_amount = models.DecimalField(<br \/>\n        max_digits=15,<br \/>\n        decimal_places=2,<br \/>\n        default=Decimal(&#8216;0.00&#8217;)<br \/>\n    )<br \/>\n    credit_amount = models.DecimalField(<br \/>\n        max_digits=15,<br \/>\n        decimal_places=2,<br \/>\n        default=Decimal(&#8216;0.00&#8242;)<br \/>\n    )<br \/>\n    status = models.CharField(<br \/>\n        max_length=10,<br \/>\n        choices=STATUS_CHOICES,<br \/>\n        default=&#8217;PENDING&#8217;<br \/>\n    )<br \/>\n    created_at = models.DateTimeField(auto_now_add=True)<br \/>\n    processed_at = models.DateTimeField(null=True, blank=True)<\/p>\n<p>    class Meta:<br \/>\n        ordering = [&#8216;-date&#8217;, &#8216;-created_at&#8217;]\n<p>    def clean(self):<br \/>\n        self._validate_period()<br \/>\n        self._validate_amounts()<br \/>\n        self._validate_date()<\/p>\n<p>    def _validate_period(self):<br \/>\n        &#8220;&#8221;&#8221;Valida que el per\u00edodo est\u00e9 activo y la entrada pueda ser modificada.&#8221;&#8221;&#8221;<br \/>\n        if not self.period_id:<br \/>\n            self.period = self._get_appropriate_period()<\/p>\n<p>        if self.period.status == &#8216;CLOSED&#8217;:<br \/>\n            raise ValidationError(<br \/>\n                &#8216;No se pueden crear o modificar entradas en per\u00edodos cerrados.&#8217;<br \/>\n            )<\/p>\n<p>    def _validate_amounts(self):<br \/>\n        &#8220;&#8221;&#8221;Valida que los montos sean coherentes.&#8221;&#8221;&#8221;<br \/>\n        if self.debit_amount &lt; 0 or self.credit_amount &lt; 0:<br \/>\n            raise ValidationError(<br \/>\n                &#8216;Los montos no pueden ser negativos.&#8217;<br \/>\n            )<\/p>\n<p>        if self.debit_amount == 0 and self.credit_amount == 0:<br \/>\n            raise ValidationError(<br \/>\n                &#8216;Al menos un monto debe ser mayor a cero.&#8217;<br \/>\n            )<\/p>\n<p>    def _validate_date(self):<br \/>\n        &#8220;&#8221;&#8221;Valida que la fecha est\u00e9 dentro del per\u00edodo.&#8221;&#8221;&#8221;<br \/>\n        if not (self.period.start_date &lt;= self.date &lt;= self.period.end_date):<br \/>\n            raise ValidationError(<br \/>\n                &#8216;La fecha debe estar dentro del per\u00edodo contable.&#8217;<br \/>\n            )<\/p>\n<p>    def _get_appropriate_period(self):<br \/>\n        &#8220;&#8221;&#8221;Obtiene el per\u00edodo activo adecuado para la fecha de la entrada.&#8221;&#8221;&#8221;<br \/>\n        period = AccountingPeriod.objects.filter(<br \/>\n            start_date__lte=self.date,<br \/>\n            end_date__gte=self.date,<br \/>\n            status=&#8217;ACTIVE&#8217;<br \/>\n        ).first()<\/p>\n<p>        if not period:<br \/>\n            raise ValidationError(<br \/>\n                &#8216;No existe un per\u00edodo activo para la fecha especificada.&#8217;<br \/>\n            )<br \/>\n        return period<\/p>\n<p>    def process(self):<br \/>\n        &#8220;&#8221;&#8221;Procesa la entrada contable.&#8221;&#8221;&#8221;<br \/>\n        if self.status != &#8216;PENDING&#8217;:<br \/>\n            raise ValidationError(&#8216;Solo se pueden procesar entradas pendientes.&#8217;)<\/p>\n<p>        self.status = &#8216;PROCESSED&#8217;<br \/>\n        self.processed_at = timezone.now()<br \/>\n        self.save()<\/p>\n<p>    def reject(self):<br \/>\n        &#8220;&#8221;&#8221;Rechaza la entrada contable.&#8221;&#8221;&#8221;<br \/>\n        if self.status != &#8216;PENDING&#8217;:<br \/>\n            raise ValidationError(&#8216;Solo se pueden rechazar entradas pendientes.&#8217;)<\/p>\n<p>        self.status = &#8216;REJECTED&#8217;<br \/>\n        self.processed_at = timezone.now()<br \/>\n        self.save()<\/p>\n<p>    def __str__(self):<br \/>\n        return f&#8221;{self.date} &#8211; {self.description}&#8221;<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>  \u062a\u0646\u0638\u06cc\u0645\u0627\u062a \u0645\u062f\u06cc\u0631\u06cc\u062a<\/p>\n<p>from django.contrib import admin<br \/>\nfrom django.utils.html import format_html<br \/>\nfrom django.contrib import messages<\/p>\n<p>@admin.register(AccountingPeriod)<br \/>\nclass AccountingPeriodAdmin(admin.ModelAdmin):<br \/>\n    list_display = [<br \/>\n        &#8216;name&#8217;,<br \/>\n        &#8216;start_date&#8217;,<br \/>\n        &#8216;end_date&#8217;,<br \/>\n        &#8216;status_display&#8217;,<br \/>\n        &#8216;balance_display&#8217;,<br \/>\n        &#8216;created_at&#8217;<br \/>\n    ]\n    list_filter = [&#8216;status&#8217;]\n    search_fields = [&#8216;name&#8217;]\n    readonly_fields = [&#8216;closed_at&#8217;]\n<p>    def status_display(self, obj):<br \/>\n        colors = {<br \/>\n            &#8216;DRAFT&#8217;: &#8216;#6c757d&#8217;,<br \/>\n            &#8216;ACTIVE&#8217;: &#8216;#28a745&#8217;,<br \/>\n            &#8216;CLOSED&#8217;: &#8216;#dc3545&#8217;,<br \/>\n        }<br \/>\n        return format_html(<br \/>\n            &#8216;&#8221;color: {}; font-weight: bold;&#8221;&gt;{}&#8217;,<br \/>\n            colors[obj.status],<br \/>\n            obj.get_status_display()<br \/>\n        )<br \/>\n    status_display.short_description = &#8216;Estado&#8217;<\/p>\n<p>    def balance_display(self, obj):<br \/>\n        if obj.is_balanced():<br \/>\n            return format_html(<br \/>\n                &#8216;&#8221;color: #28a745;&#8221;&gt;Balanceado&#8217;<br \/>\n            )<br \/>\n        return format_html(<br \/>\n            &#8216;&#8221;color: #dc3545;&#8221;&gt;Desbalanceado&#8217;<br \/>\n        )<br \/>\n    balance_display.short_description = &#8216;Balance&#8217;<\/p>\n<p>    def get_readonly_fields(self, request, obj=None):<br \/>\n        if obj and obj.status == &#8216;CLOSED&#8217;:<br \/>\n            return [f.name for f in obj._meta.fields]\n        return self.readonly_fields<\/p>\n<p>    actions = [&#8216;activate_periods&#8217;, &#8216;close_periods&#8217;]\n<p>    def activate_periods(self, request, queryset):<br \/>\n        for period in queryset:<br \/>\n            try:<br \/>\n                period.activate()<br \/>\n                self.message_user(<br \/>\n                    request,<br \/>\n                    f&#8217;Per\u00edodo {period.name} activado exitosamente.&#8217;,<br \/>\n                    messages.SUCCESS<br \/>\n                )<br \/>\n            except ValidationError as e:<br \/>\n                self.message_user(<br \/>\n                    request,<br \/>\n                    f&#8221;Error al activar {period.name}: {str(e)}&#8221;,<br \/>\n                    messages.ERROR<br \/>\n                )<br \/>\n    activate_periods.short_description = &#8220;Activar per\u00edodos seleccionados&#8221;<\/p>\n<p>    def close_periods(self, request, queryset):<br \/>\n        for period in queryset:<br \/>\n            try:<br \/>\n                period.close()<br \/>\n                self.message_user(<br \/>\n                    request,<br \/>\n                    f&#8217;Per\u00edodo {period.name} cerrado exitosamente.&#8217;,<br \/>\n                    messages.SUCCESS<br \/>\n                )<br \/>\n            except ValidationError as e:<br \/>\n                self.message_user(<br \/>\n                    request,<br \/>\n                    f&#8221;Error al cerrar {period.name}: {str(e)}&#8221;,<br \/>\n                    messages.ERROR<br \/>\n                )<br \/>\n    close_periods.short_description = &#8220;Cerrar per\u00edodos seleccionados&#8221;<\/p>\n<p>@admin.register(AccountingEntry)<br \/>\nclass AccountingEntryAdmin(admin.ModelAdmin):<br \/>\n    list_display = [<br \/>\n        &#8216;date&#8217;,<br \/>\n        &#8216;description&#8217;,<br \/>\n        &#8216;debit_amount&#8217;,<br \/>\n        &#8216;credit_amount&#8217;,<br \/>\n        &#8216;status_display&#8217;,<br \/>\n        &#8216;period&#8217;<br \/>\n    ]\n    list_filter = [&#8216;period&#8217;, &#8216;status&#8217;, &#8216;date&#8217;]\n    search_fields = [&#8216;description&#8217;]\n    actions = [&#8216;process_entries&#8217;, &#8216;reject_entries&#8217;]\n<p>    def status_display(self, obj):<br \/>\n        colors = {<br \/>\n            &#8216;PENDING&#8217;: &#8216;#ffc107&#8217;,<br \/>\n            &#8216;PROCESSED&#8217;: &#8216;#28a745&#8217;,<br \/>\n            &#8216;REJECTED&#8217;: &#8216;#dc3545&#8217;,<br \/>\n        }<br \/>\n        return format_html(<br \/>\n            &#8216;&#8221;color: {}; font-weight: bold;&#8221;&gt;{}&#8217;,<br \/>\n            colors[obj.status],<br \/>\n            obj.get_status_display()<br \/>\n        )<br \/>\n    status_display.short_description = &#8216;Estado&#8217;<\/p>\n<p>    def get_readonly_fields(self, request, obj=None):<br \/>\n        if obj and (obj.status != &#8216;PENDING&#8217; or obj.period.status == &#8216;CLOSED&#8217;):<br \/>\n            return [f.name for f in obj._meta.fields]\n        return []\n<p>    def has_delete_permission(self, request, obj=None):<br \/>\n        if obj and (obj.status != &#8216;PENDING&#8217; or obj.period.status == &#8216;CLOSED&#8217;):<br \/>\n            return False<br \/>\n        return super().has_delete_permission(request, obj)<\/p>\n<p>    def process_entries(self, request, queryset):<br \/>\n        for entry in queryset:<br \/>\n            try:<br \/>\n                entry.process()<br \/>\n                self.message_user(<br \/>\n                    request,<br \/>\n                    f&#8217;Entrada {entry} procesada exitosamente.&#8217;,<br \/>\n                    messages.SUCCESS<br \/>\n                )<br \/>\n            except ValidationError as e:<br \/>\n                self.message_user(<br \/>\n                    request,<br \/>\n                    f&#8221;Error al procesar entrada {entry}: {str(e)}&#8221;,<br \/>\n                    messages.ERROR<br \/>\n                )<br \/>\n    process_entries.short_description = &#8220;Procesar entradas seleccionadas&#8221;<\/p>\n<p>## Tests Unitarios<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>\u067e\u0627\u06cc\u062a\u0648\u0646\u0627\u0632 django.test import TestCase\u0627\u0632 django.core.exceptions import ValidationError\u0627\u0632 \u0648\u0627\u0631\u062f\u0627\u062a \u0627\u0639\u0634\u0627\u0631\u06cc \u0627\u0639\u0634\u0627\u0631\u06cc\u0627\u0632 \u062a\u0627\u0631\u06cc\u062e \u0648\u0627\u0631\u062f\u0627\u062a \u062a\u0627\u0631\u06cc\u062e\u060c timedelta\u0627\u0632 .models import AccountingPeriod, AccountingEntry<\/p>\n<p>\u06a9\u0644\u0627\u0633 AccountingPeriodTests (TestCase):def setUp(self):self.period = AccountingPeriod.objects.create(name = &#8220;\u062f\u0648\u0631\u0647 \u062a\u0633\u062a&#8221;\u060cstart_date=date(2024, 1, 1),end_date=date(2024, 1, 31),status=&#8221;DRAFT&#8221;)<\/p>\n<p>def test_period_lifecycle(self):<br \/>\n    &#8220;&#8221;&#8221;Test del ciclo de vida completo de un per\u00edodo&#8221;&#8221;&#8221;<br \/>\n    # Verificar estado inicial<br \/>\n    self.assertEqual(self.period.status, &#8216;DRAFT&#8217;)<\/p>\n<p>    # Activar per\u00edodo<br \/>\n    self.period.activate()<br \/>\n    self.assertEqual(self.period.status, &#8216;ACTIVE&#8217;)<\/p>\n<p>    # Crear entradas balanceadas<br \/>\n    AccountingEntry.objects.create(<br \/>\n        period=self.period,<br \/>\n        date=date(2024, 1, 15),<br \/>\n        description=&#8221;Test Entry 1&#8243;,<br \/>\n        debit_amount=Decimal(&#8216;100.00&#8217;),<br \/>\n        credit_amount=Decimal(&#8216;0.00&#8217;),<br \/>\n        status=&#8221;PROCESSED&#8221;<br \/>\n    )<br \/>\n    AccountingEntry.objects.create(<br \/>\n        period=self.period,<br \/>\n        date=date(2024, 1, 15),<br \/>\n        description=&#8221;Test Entry 2&#8243;,<br \/>\n        debit_amount=Decimal(&#8216;0.00&#8217;),<br \/>\n        credit_amount=Decimal(&#8216;100.00&#8217;),<br \/>\n        status=&#8221;PROCESSED&#8221;<br \/>\n    )<\/p>\n<p>    # Cerrar per\u00edodo<br \/>\n    self.period.close()<br \/>\n    self.assertEqual(self.period.status, &#8216;CLOSED&#8217;)<br \/>\n    self.assertIsNotNone(self.period.closed_at)<\/p>\n<p>def test_overlapping_periods(self):<br \/>\n    &#8220;&#8221;&#8221;Test de validaci\u00f3n de per\u00edodos superpuestos&#8221;&#8221;&#8221;<br \/>\n    with self.assertRaises(ValidationError):<br \/>\n        AccountingPeriod.objects.create(<br \/>\n            name=&#8221;Overlapping Period&#8221;,<br \/>\n            start_date=date(2024, 1, 15),<br \/>\n            end_date=date(2024, 2, 15),<br \/>\n            status=&#8221;DRAFT&#8221;<br \/>\n        )<\/p>\n<p>def test_invalid_status_transition(self):<br \/>\n    &#8220;&#8221;&#8221;Test de transiciones de estado inv\u00e1lidas&#8221;&#8221;&#8221;<br \/>\n    # No se puede pasar directamente de DRAFT a CLOSED<br \/>\n    self.period.status=&#8221;CLOSED&#8221;<br \/>\n    with self.assertRaises(ValidationError):<br \/>\n        self.period.save()<\/p>\n<p>    # No se puede reabrir un per\u00edodo cerrado<br \/>\n    self.period.activate()<br \/>\n    self.period.close()<br \/>\n    self.period.status=&#8221;ACTIVE&#8221;<br \/>\n    with self.assertRaises(ValidationError):<br \/>\n        self.period.save()<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>\u06a9\u0644\u0627\u0633 AccountingEntryTests (TestCase):def setUp(self):self.period = AccountingPeriod.objects.create(name = &#8220;\u062f\u0648\u0631\u0647 \u062a\u0633\u062a&#8221;\u060cstart_date=date(2024, 1, 1),end_date=date(2024, 1, 31),\u0648\u0636\u0639\u06cc\u062a &#8220;\u0641\u0639\u0627\u0644&#8221;)<\/p>\n<p>def test_entry_validation(self):<br \/>\n    &#8220;&#8221;&#8221;Test de validaciones b\u00e1sicas de entradas&#8221;&#8221;&#8221;<br \/>\n    # Entrada con montos negativos<br \/>\n    with self.assertRaises(ValidationError):<br \/>\n        AccountingEntry.objects.create(<br \/>\n            period=self.period,<br \/>\n            date=date(2024, 1, 15),<br \/>\n            description=&#8221;Invalid Entry&#8221;,<br \/>\n            debit_amount=Decimal(&#8216;-100.00&#8217;),<br \/>\n            credit_amount=Decimal(&#8216;0.00&#8217;)<br \/>\n        )<\/p>\n<p>    # Entrada fuera del per\u00edodo<br \/>\n    with self.assertRaises(ValidationError):<br \/>\n        AccountingEntry.objects.create(<br \/>\n            period=self.period,<br \/>\n            date=date(2024, 2, 1),<br \/>\n            description=&#8221;Invalid Entry&#8221;,<br \/>\n            debit_amount=Decimal(&#8216;100.00&#8217;),<br \/>\n            credit_amount=Decimal(&#8216;0.00&#8217;)<br \/>\n        )<\/p>\n<p>def test_entry_processing(self):<br \/>\n    &#8220;&#8221;&#8221;Test del procesamiento de entradas&#8221;&#8221;&#8221;<br \/>\n    entry = AccountingEntry.objects.create(<br \/>\n        period=self.period,<br \/>\n        date=date(2024, 1, 15),<br \/>\n        description=&#8221;Test Entry&#8221;,<br \/>\n        debit_amount=Decimal(&#8216;100.00&#8217;),<br \/>\n        credit_amount=Decimal(&#8216;0.00&#8217;)<br \/>\n    )<\/p>\n<p>    # Procesar entrada<br \/>\n    entry.process()<br \/>\n    self.assertEqual(entry.status, &#8216;PROCESSED&#8217;)<br \/>\n    self.assertIsNotNone(entry.processed_at)<\/p>\n<p>    # No se puede procesar una entrada ya procesada<br \/>\n    with self.assertRaises(ValidationError):<br \/>\n        entry.process()<\/p>\n<p>def test_closed_period_modifications(self):<br \/>\n    &#8220;&#8221;&#8221;Test de modificaciones en per\u00edodo cerrado&#8221;&#8221;&#8221;<br \/>\n    self.period.close()<\/p>\n<p>    # No se pueden crear nuevas entradas<br \/>\n    with self.assertRaises(ValidationError):<br \/>\n        AccountingEntry.objects.create(<br \/>\n            period=self.period,<br \/>\n            date=date(2024, 1, 15),<br \/>\n            description=&#8221;Test Entry&#8221;,<br \/>\n            debit_amount=Decimal(&#8216;100.00&#8217;),<br \/>\n            credit_amount=Decimal(&#8216;0.00&#8217;)<br \/>\n        )<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>## Ejemplo Pr\u00e1ctico: Sistema de Transferencias<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>\u067e\u0627\u06cc\u062a\u0648\u0646<\/p>\n<p>\u0627\u0632 \u0648\u0627\u0631\u062f\u0627\u062a \u0627\u0639\u0634\u0627\u0631\u06cc \u0627\u0639\u0634\u0627\u0631\u06cc\u0627\u0632 \u062a\u0627\u0631\u06cc\u062e \u0648\u0627\u0631\u062f\u0627\u062a \u062a\u0627\u0631\u06cc\u062e<\/p>\n<p>def transfer_between_accounts(period_id: int,\u062a\u0627\u0631\u06cc\u062e: \u062a\u0627\u0631\u06cc\u062e\u060c\u0645\u0642\u062f\u0627\u0631: \u0627\u0639\u0634\u0627\u0631\u06cc\u060c\u062a\u0648\u0636\u06cc\u062d\u0627\u062a: \u062e) -> \u062a\u0627\u067e\u0644:&#8221;&#8221;&#8221;\u0628\u0627 \u0627\u06cc\u062c\u0627\u062f \u062f\u0648 \u0648\u0631\u0648\u062f\u06cc \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc\u060c \u0628\u06cc\u0646 \u062d\u0633\u0627\u0628 \u0647\u0627 \u0627\u0646\u062a\u0642\u0627\u0644 \u0627\u06cc\u062c\u0627\u062f \u06a9\u0646\u06cc\u062f.<\/p>\n<p>Returns:<br \/>\n    tuple: (debit_entry, credit_entry)<br \/>\n&#8220;&#8221;&#8221;<br \/>\ntry:<br \/>\n    period = AccountingPeriod.objects.get(id=period_id)<\/p>\n<p>    # Crear entrada de d\u00e9bito<br \/>\n    debit_entry = AccountingEntry.objects.create(<br \/>\n        period=period,<br \/>\n        date=date,<br \/>\n        description=f&#8221;D\u00c9BITO &#8211; {description}&#8221;,<br \/>\n        debit_amount=amount,<br \/>\n        credit_amount=Decimal(&#8216;0.00&#8217;)<br \/>\n    )<\/p>\n<p>    # Crear entrada de cr\u00e9dito<br \/>\n    credit_entry = AccountingEntry.objects.create(<br \/>\n        period=period,<br \/>\n        date=date,<br \/>\n        description=f&#8221;CR\u00c9DITO &#8211; {description}&#8221;,<br \/>\n        debit_amount=Decimal(&#8216;0.00&#8217;),<br \/>\n        credit_amount=amount<br \/>\n    )<\/p>\n<p>    # Procesar ambas entradas<br \/>\n    debit_entry.process()<br \/>\n    credit_entry.process()<\/p>\n<p>    return debit_entry, credit_entry<\/p>\n<p>except AccountingPeriod.DoesNotExist:<br \/>\n    raise ValidationError(&#8220;Per\u00edodo contable no encontrado&#8221;)<br \/>\nexcept Exception as e:<br \/>\n    raise ValidationError(f&#8221;Error en la transferencia: {str(e)}&#8221;)<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>\u0633\u0639\u06cc \u06a9\u0646\u06cc\u062f:period = AccountingPeriod.objects.get(\u0634\u0631\u0648\u0639_\u062a\u0627\u0631\u06cc\u062e_lte=date(2024, 1, 15),\u067e\u0627\u06cc\u0627\u0646_\u062a\u0627\u0631\u06cc\u062e_gte=date(2024\u060c 1\u060c 15)\u060c\u0648\u0636\u0639\u06cc\u062a &#8220;\u0641\u0639\u0627\u0644&#8221;)<\/p>\n<p>debit, credit = transfer_between_accounts(<br \/>\n    period_id=period.id,<br \/>\n    date=date(2024, 1, 15),<br \/>\n    amount=Decimal(&#8216;1000.00&#8217;),<br \/>\n    description=&#8221;Transferencia entre cuentas&#8221;<br \/>\n)<\/p>\n<p>print(f&#8221;Transferencia exitosa: {debit.id}, {credit.id}&#8221;)<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>\u0628\u0647 \u062c\u0632 ValidationError \u0628\u0647 \u0635\u0648\u0631\u062a e:print(f&#8221;\u062e\u0637\u0627: {str(e)}&#8221;)<\/p>\n<p>## Queries \u00datiles<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>\u067e\u0627\u06cc\u062a\u0648\u0646<\/p>\n<p>def check_period_balance(period_id: int) -> dict:period = AccountingPeriod.objects.get(id=period_id)\u0645\u062c\u0645\u0648\u0639 = period.entries.aggregate(total_debit=Sum(&#39;debit_amount&#39;),total_credit=Sum(&#39;credit_amount&#39;))<\/p>\n<p>return {<br \/>\n    &#8216;period_name&#8217;: period.name,<br \/>\n    &#8216;total_debit&#8217;: totals[&#8216;total_debit&#8217;] or Decimal(&#8216;0.00&#8217;),<br \/>\n    &#8216;total_credit&#8217;: totals[&#8216;total_credit&#8217;] or Decimal(&#8216;0.00&#8217;),<br \/>\n    &#8216;is_balanced&#8217;: period.is_balanced()<br \/>\n}<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>def get_pending_entries(period_id: int) -> QuerySet:\u0628\u0627\u0632\u06af\u0634\u062a AccountingEntry.objects.filter(period_id=period_id\u060c\u0648\u0636\u0639\u06cc\u062a &#8220;\u062f\u0631 \u0627\u0646\u062a\u0638\u0627\u0631&#8221;)<\/p>\n<p>def get_period_summary(period_id: int) -> dict:period = AccountingPeriod.objects.get(id=period_id)entries_summary = period.entries.aggregate(total_entries=Count(&#39;id&#39;),pending_entries=Count(&#39;id&#39;, filter=Q(status=&#8221;PENDING&#8221;))processed_entries=Count(&#39;id&#39;, filter=Q(status=&#8221;PROCESSED&#8221;))rejected_entries=Count(&#39;id&#39;, filter=Q(status=&#8221;REJECTED&#8221;))total_debit=Sum(&#39;debit_amount&#39;),total_credit=Sum(&#39;credit_amount&#39;))<\/p>\n<p>return {<br \/>\n    &#8216;period_name&#8217;: period.name,<br \/>\n    &#8216;status&#8217;: period.get_status_display(),<br \/>\n    &#8216;duration&#8217;: (period.end_date &#8211; period.start_date).days + 1,<br \/>\n    &#8216;total_entries&#8217;: entries_summary[&#8216;total_entries&#8217;],<br \/>\n    &#8216;pending_entries&#8217;: entries_summary[&#8216;pending_entries&#8217;],<br \/>\n    &#8216;processed_entries&#8217;: entries_summary[&#8216;processed_entries&#8217;],<br \/>\n    &#8216;rejected_entries&#8217;: entries_summary[&#8216;rejected_entries&#8217;],<br \/>\n    &#8216;total_debit&#8217;: entries_summary[&#8216;total_debit&#8217;] or Decimal(&#8216;0.00&#8217;),<br \/>\n    &#8216;total_credit&#8217;: entries_summary[&#8216;total_credit&#8217;] or Decimal(&#8216;0.00&#8217;)<br \/>\n}<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<p>## Mejores Pr\u00e1cticas<\/p>\n<p>1. **Validaciones de Seguridad**<br \/>\n   &#8211; Usar `PROTECT` en foreign keys para prevenir eliminaci\u00f3n en cascada<br \/>\n   &#8211; Implementar validaciones a nivel de modelo<br \/>\n   &#8211; Mantener registro de auditor\u00eda de cambios importantes<\/p>\n<p>2. **Manejo de Errores**<br \/>\n   &#8211; Usar excepciones espec\u00edficas y mensajes claros<br \/>\n   &#8211; Implementar rollback en operaciones complejas<br \/>\n   &#8211; Registrar errores cr\u00edticos en logs<\/p>\n<p>3. **Patrones de Dise\u00f1o**<br \/>\n   &#8211; State Pattern para estados de per\u00edodo y entradas<br \/>\n   &#8211; Factory Method para crear entradas relacionadas<br \/>\n   &#8211; Observer Pattern para auditor\u00eda y notificaciones<\/p>\n<p>## Conclusi\u00f3n<\/p>\n<p>Este sistema proporciona una base s\u00f3lida para manejar per\u00edodos contables en Django, con:<br \/>\n&#8211; Validaciones robustas para mantener la integridad de los datos<br \/>\n&#8211; Flujo claro de estados para per\u00edodos y entradas<br \/>\n&#8211; Sistema extensible para diferentes tipos de operaciones contables<\/p>\n<p>    \u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/p>\n<p>    \u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/p>\n<div data-article-id=\"2088008\" id=\"article-body\">\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_85 counter-hierarchy ez-toc-counter-rtl ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">\u0641\u0647\u0631\u0633\u062a \u0645\u0637\u0627\u0644\u0628<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"\u062a\u063a\u06cc\u06cc\u0631 \u0648\u0636\u0639\u06cc\u062a \u0641\u0647\u0631\u0633\u062a \u0645\u0637\u0627\u0644\u0628\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/nabfollower.com\/blog\/contabilidad-para-django-developers-periodos-contables-1c30\/#%D9%85%D9%82%D8%AF%D9%85%D9%87\" >\u0645\u0642\u062f\u0645\u0647<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/nabfollower.com\/blog\/contabilidad-para-django-developers-periodos-contables-1c30\/#%D9%BE%DB%8C%D8%B4_%D9%86%DB%8C%D8%A7%D8%B2%D9%87%D8%A7\" >\u067e\u06cc\u0634 \u0646\u06cc\u0627\u0632\u0647\u0627<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/nabfollower.com\/blog\/contabilidad-para-django-developers-periodos-contables-1c30\/#%D9%85%D9%81%D8%A7%D9%87%DB%8C%D9%85_%DA%A9%D9%84%DB%8C%D8%AF%DB%8C\" >\u0645\u0641\u0627\u0647\u06cc\u0645 \u06a9\u0644\u06cc\u062f\u06cc<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/nabfollower.com\/blog\/contabilidad-para-django-developers-periodos-contables-1c30\/#%D8%AF%D9%88%D8%B1%D9%87_%D8%AD%D8%B3%D8%A7%D8%A8%D8%AF%D8%A7%D8%B1%DB%8C_%D9%85%D8%B9%D8%A7%D9%85%D9%84%D9%87_%D9%86%D9%87%D8%A7%DB%8C%DB%8C\" >\u062f\u0648\u0631\u0647 \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc: \u0645\u0639\u0627\u0645\u0644\u0647 \u0646\u0647\u0627\u06cc\u06cc<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/nabfollower.com\/blog\/contabilidad-para-django-developers-periodos-contables-1c30\/#%D8%A7%DB%8C%D8%A7%D9%84%D8%A7%D8%AA_%D8%AF%D9%88%D8%B1%D9%87\" >\u0627\u06cc\u0627\u0644\u0627\u062a \u062f\u0648\u0631\u0647<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/nabfollower.com\/blog\/contabilidad-para-django-developers-periodos-contables-1c30\/#%D9%BE%DB%8C%D8%A7%D8%AF%D9%87_%D8%B3%D8%A7%D8%B2%DB%8C\" >\u067e\u06cc\u0627\u062f\u0647 \u0633\u0627\u0632\u06cc<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/nabfollower.com\/blog\/contabilidad-para-django-developers-periodos-contables-1c30\/#%D9%85%D8%AF%D9%84_%D9%87%D8%A7%DB%8C_%D9%BE%D8%A7%DB%8C%D9%87\" >\u0645\u062f\u0644 \u0647\u0627\u06cc \u067e\u0627\u06cc\u0647<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/nabfollower.com\/blog\/contabilidad-para-django-developers-periodos-contables-1c30\/#%D8%AA%D9%86%D8%B8%DB%8C%D9%85%D8%A7%D8%AA_%D9%85%D8%AF%DB%8C%D8%B1%DB%8C%D8%AA\" >\u062a\u0646\u0638\u06cc\u0645\u0627\u062a \u0645\u062f\u06cc\u0631\u06cc\u062a<\/a><\/li><\/ul><\/li><\/ul><\/nav><\/div>\n<h2><span class=\"ez-toc-section\" id=\"%D9%85%D9%82%D8%AF%D9%85%D9%87\"><\/span>\n<p>  \u0645\u0642\u062f\u0645\u0647<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>\u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u06cc\u06a9 \u062a\u0648\u0633\u0639\u0647 \u062f\u0647\u0646\u062f\u0647 \u062c\u0646\u06af\u0648\u060c \u0627\u062d\u062a\u0645\u0627\u0644\u0627\u064b \u0628\u0627 \u0633\u06cc\u0633\u062a\u0645 \u0647\u0627\u06cc\u06cc \u06a9\u0627\u0631 \u06a9\u0631\u062f\u0647 \u0627\u06cc\u062f \u06a9\u0647 \u062f\u0627\u062f\u0647 \u0647\u0627\u06cc \u0632\u0645\u0627\u0646\u06cc \u0631\u0627 \u0645\u062f\u06cc\u0631\u06cc\u062a \u0645\u06cc \u06a9\u0646\u0646\u062f\u060c \u0627\u0645\u0627 \u062f\u0648\u0631\u0647 \u0647\u0627\u06cc \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc \u0645\u062a\u0641\u0627\u0648\u062a \u0627\u0633\u062a. \u0633\u06cc\u0633\u062a\u0645\u06cc \u0631\u0627 \u062a\u0635\u0648\u0631 \u06a9\u0646\u06cc\u062f \u06a9\u0647 \u062f\u0631 \u0622\u0646 \u0628\u0627\u06cc\u062f \u0627\u0637\u0645\u06cc\u0646\u0627\u0646 \u062d\u0627\u0635\u0644 \u06a9\u0646\u06cc\u062f \u06a9\u0647 \u062a\u0645\u0627\u0645 \u0639\u0645\u0644\u06cc\u0627\u062a \u0645\u0627\u0644\u06cc \u0628\u0647 \u062f\u0631\u0633\u062a\u06cc \u062f\u0631 \u062f\u0648\u0631\u0647 \u0647\u0627\u06cc \u0632\u0645\u0627\u0646\u06cc \u062e\u0627\u0635 \u0633\u0627\u0632\u0645\u0627\u0646\u062f\u0647\u06cc \u0634\u062f\u0647 \u0627\u0646\u062f \u0648 \u0632\u0645\u0627\u0646\u06cc \u06a9\u0647 \u06cc\u06a9 \u062f\u0648\u0631\u0647 \u0628\u0633\u062a\u0647 \u0645\u06cc \u0634\u0648\u062f\u060c \u062f\u0627\u062f\u0647 \u0647\u0627 \u062a\u063a\u06cc\u06cc\u0631 \u0646\u0627\u067e\u0630\u06cc\u0631 \u0647\u0633\u062a\u0646\u062f.<\/p>\n<p>\u0627\u06cc\u0646 \u0622\u0645\u0648\u0632\u0634 \u0634\u0645\u0627 \u0631\u0627 \u062f\u0631 \u067e\u06cc\u0627\u062f\u0647 \u0633\u0627\u0632\u06cc \u06cc\u06a9 \u0633\u06cc\u0633\u062a\u0645 \u062f\u0648\u0631\u0647 \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc \u0642\u0648\u06cc \u06a9\u0647 \u06cc\u06a9\u067e\u0627\u0631\u0686\u06af\u06cc \u062f\u0627\u062f\u0647 \u0647\u0627\u06cc \u0645\u0627\u0644\u06cc \u0631\u0627 \u062a\u0636\u0645\u06cc\u0646 \u0645\u06cc \u06a9\u0646\u062f\u060c \u062a\u0646\u0647\u0627 \u0628\u0627 \u0627\u0633\u062a\u0641\u0627\u062f\u0647 \u0627\u0632 \u0627\u062f\u0645\u06cc\u0646 \u062c\u0646\u06af\u0648 \u0631\u0627\u0647\u0646\u0645\u0627\u06cc\u06cc \u0645\u06cc \u06a9\u0646\u062f. \u0634\u0645\u0627 \u06cc\u0627\u062f \u062e\u0648\u0627\u0647\u06cc\u062f \u06af\u0631\u0641\u062a \u06a9\u0647 \u0628\u0627\u0632\u060c \u0639\u0645\u0644\u06cc\u0627\u062a \u0648 \u0628\u0633\u062a\u0647 \u0634\u062f\u0646 \u062f\u0648\u0631\u0647 \u0647\u0627\u06cc \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc \u0631\u0627 \u0628\u0647 \u0647\u0645\u0631\u0627\u0647 \u062a\u0645\u0627\u0645 \u0627\u0639\u062a\u0628\u0627\u0631\u0633\u0646\u062c\u06cc \u0647\u0627\u06cc \u0644\u0627\u0632\u0645 \u0628\u0631\u0627\u06cc \u062d\u0641\u0638 \u06cc\u06a9\u067e\u0627\u0631\u0686\u06af\u06cc \u062f\u0627\u062f\u0647 \u0647\u0627 \u0645\u062f\u06cc\u0631\u06cc\u062a \u06a9\u0646\u06cc\u062f.<\/p>\n<p>\u067e\u0633 \u0627\u0632 \u062a\u06a9\u0645\u06cc\u0644\u060c \u0634\u0645\u0627 \u0642\u0627\u062f\u0631 \u062e\u0648\u0627\u0647\u06cc\u062f \u0628\u0648\u062f \u0633\u06cc\u0633\u062a\u0645\u06cc \u0631\u0627 \u067e\u06cc\u0627\u062f\u0647 \u0633\u0627\u0632\u06cc \u06a9\u0646\u06cc\u062f \u06a9\u0647 \u0628\u0647 \u0627\u0635\u0648\u0644 \u0627\u0648\u0644\u06cc\u0647 \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc \u067e\u0627\u06cc\u0628\u0646\u062f \u0628\u0627\u0634\u062f \u0648 \u062f\u0631 \u0639\u06cc\u0646 \u062d\u0627\u0644 \u0628\u0647\u062a\u0631\u06cc\u0646 \u0634\u06cc\u0648\u0647 \u0647\u0627\u06cc \u062a\u0648\u0633\u0639\u0647 \u0631\u0627 \u062f\u0631 \u062c\u0646\u06af\u0648 \u062d\u0641\u0638 \u06a9\u0646\u06cc\u062f.<\/p>\n<h2><span class=\"ez-toc-section\" id=\"%D9%BE%DB%8C%D8%B4_%D9%86%DB%8C%D8%A7%D8%B2%D9%87%D8%A7\"><\/span>\n<p>  \u067e\u06cc\u0634 \u0646\u06cc\u0627\u0632\u0647\u0627<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<ul>\n<li>\u067e\u0627\u06cc\u062a\u0648\u0646 3.12<\/li>\n<li>\u062c\u0646\u06af\u0648 5.0<\/li>\n<li>\u062f\u0627\u0646\u0634 \u0627\u0648\u0644\u06cc\u0647 \u0645\u062f\u0644 \u0647\u0627 \u062f\u0631 \u062c\u0646\u06af\u0648<\/li>\n<li>SQLite \u06cc\u0627 PostgreSQL (\u062a\u0648\u0635\u06cc\u0647 \u0645\u06cc \u0634\u0648\u062f)<\/li>\n<\/ul>\n<h2><span class=\"ez-toc-section\" id=\"%D9%85%D9%81%D8%A7%D9%87%DB%8C%D9%85_%DA%A9%D9%84%DB%8C%D8%AF%DB%8C\"><\/span>\n<p>  \u0645\u0641\u0627\u0647\u06cc\u0645 \u06a9\u0644\u06cc\u062f\u06cc<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<h3><span class=\"ez-toc-section\" id=\"%D8%AF%D9%88%D8%B1%D9%87_%D8%AD%D8%B3%D8%A7%D8%A8%D8%AF%D8%A7%D8%B1%DB%8C_%D9%85%D8%B9%D8%A7%D9%85%D9%84%D9%87_%D9%86%D9%87%D8%A7%DB%8C%DB%8C\"><\/span>\n<p>  \u062f\u0648\u0631\u0647 \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc: \u0645\u0639\u0627\u0645\u0644\u0647 \u0646\u0647\u0627\u06cc\u06cc<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\u06cc\u06a9 \u062f\u0648\u0631\u0647 \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc \u0631\u0627 \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u06cc\u06a9 \u0645\u0639\u0627\u0645\u0644\u0647 \u067e\u0627\u06cc\u06af\u0627\u0647 \u062f\u0627\u062f\u0647 \u062f\u0631 \u0645\u0642\u06cc\u0627\u0633 \u0628\u0632\u0631\u06af \u062f\u0631 \u0646\u0638\u0631 \u0628\u06af\u06cc\u0631\u06cc\u062f:<\/p>\n<ul>\n<li>\u0628\u0627\u0632 \u06a9\u0631\u062f\u0646 \u0645\u0627\u0646\u0646\u062f \u0634\u0631\u0648\u0639 \u06cc\u06a9 \u0645\u0639\u0627\u0645\u0644\u0647 \u0627\u0633\u062a (<code>BEGIN TRANSACTION<\/code>)<\/li>\n<li>\u0639\u0645\u0644\u06cc\u0627\u062a \u0645\u0627\u0646\u0646\u062f \u067e\u0631\u0633 \u0648 \u062c\u0648 \u062f\u0631 \u062a\u0631\u0627\u06a9\u0646\u0634 \u0647\u0633\u062a\u0646\u062f<\/li>\n<li>\u0628\u0633\u062a\u0646 \u0645\u0627\u0646\u0646\u062f \u0627\u062c\u0631\u0627\u06cc \u0622\u0646 \u0627\u0633\u062a <code>COMMIT<\/code>\n<\/li>\n<\/ul>\n<h3><span class=\"ez-toc-section\" id=\"%D8%A7%DB%8C%D8%A7%D9%84%D8%A7%D8%AA_%D8%AF%D9%88%D8%B1%D9%87\"><\/span>\n<p>  \u0627\u06cc\u0627\u0644\u0627\u062a \u062f\u0648\u0631\u0647<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\u0645\u0634\u0627\u0628\u0647 \u062d\u0627\u0644\u062a \u0647\u0627\u06cc \u06cc\u06a9 \u0634\u06cc \u062f\u0631 \u062d\u0627\u0641\u0638\u0647:<\/p>\n<ul>\n<li>\n<code>DRAFT<\/code>: \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u06cc\u06a9 \u0634\u06cc\u0621 \u0646\u0645\u0648\u0646\u0647 \u0633\u0627\u0632\u06cc \u0634\u062f\u0647 \u0627\u0645\u0627 \u0630\u062e\u06cc\u0631\u0647 \u0646\u0634\u062f\u0647 \u0627\u0633\u062a<\/li>\n<li>\n<code>ACTIVE<\/code>: \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u06cc\u06a9 \u0634\u06cc \u062f\u0631 \u067e\u0627\u06cc\u06af\u0627\u0647 \u062f\u0627\u062f\u0647 \u0628\u0627\u0642\u06cc \u0645\u0627\u0646\u062f<\/li>\n<li>\n<code>CLOSED<\/code>: \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u06cc\u06a9 \u0634\u06cc\u0621 \u062a\u063a\u06cc\u06cc\u0631\u0646\u0627\u067e\u0630\u06cc\u0631<\/li>\n<\/ul>\n<h2><span class=\"ez-toc-section\" id=\"%D9%BE%DB%8C%D8%A7%D8%AF%D9%87_%D8%B3%D8%A7%D8%B2%DB%8C\"><\/span>\n<p>  \u067e\u06cc\u0627\u062f\u0647 \u0633\u0627\u0632\u06cc<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<h3><span class=\"ez-toc-section\" id=\"%D9%85%D8%AF%D9%84_%D9%87%D8%A7%DB%8C_%D9%BE%D8%A7%DB%8C%D9%87\"><\/span>\n<p>  \u0645\u062f\u0644 \u0647\u0627\u06cc \u067e\u0627\u06cc\u0647<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight python\"><code><span class=\"kn\">from<\/span> <span class=\"n\">django.db<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">models<\/span>\n<span class=\"kn\">from<\/span> <span class=\"n\">django.core.exceptions<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">ValidationError<\/span>\n<span class=\"kn\">from<\/span> <span class=\"n\">django.utils<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">timezone<\/span>\n<span class=\"kn\">from<\/span> <span class=\"n\">decimal<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">Decimal<\/span>\n<span class=\"kn\">from<\/span> <span class=\"n\">django.db.models<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">Sum<\/span>\n<span class=\"kn\">from<\/span> <span class=\"n\">datetime<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">datetime<\/span>\n\n<span class=\"k\">class<\/span> <span class=\"nc\">AccountingPeriod<\/span><span class=\"p\">(<\/span><span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">Model<\/span><span class=\"p\">):<\/span>\n    <span class=\"sh\">\"\"\"<\/span><span class=\"s\">\n    Representa un per\u00edodo contable con su ciclo de vida completo.\n    Similar a una transacci\u00f3n de base de datos a gran escala.\n    <\/span><span class=\"sh\">\"\"\"<\/span>\n    <span class=\"n\">STATUS_CHOICES<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span>\n        <span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">DRAFT<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> <span class=\"sh\">'<\/span><span class=\"s\">Borrador<\/span><span class=\"sh\">'<\/span><span class=\"p\">),<\/span>\n        <span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">ACTIVE<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> <span class=\"sh\">'<\/span><span class=\"s\">Activo<\/span><span class=\"sh\">'<\/span><span class=\"p\">),<\/span>\n        <span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">CLOSED<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> <span class=\"sh\">'<\/span><span class=\"s\">Cerrado<\/span><span class=\"sh\">'<\/span><span class=\"p\">),<\/span>\n    <span class=\"p\">]<\/span>\n\n    <span class=\"n\">name<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">CharField<\/span><span class=\"p\">(<\/span>\n        <span class=\"n\">max_length<\/span><span class=\"o\">=<\/span><span class=\"mi\">100<\/span><span class=\"p\">,<\/span>\n        <span class=\"n\">unique<\/span><span class=\"o\">=<\/span><span class=\"bp\">True<\/span><span class=\"p\">,<\/span>\n        <span class=\"n\">help_text<\/span><span class=\"o\">=<\/span><span class=\"sh\">\"<\/span><span class=\"s\">Ejemplo: <\/span><span class=\"sh\">'<\/span><span class=\"s\">Enero 2024<\/span><span class=\"sh\">'\"<\/span>\n    <span class=\"p\">)<\/span>\n    <span class=\"n\">start_date<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">DateField<\/span><span class=\"p\">()<\/span>\n    <span class=\"n\">end_date<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">DateField<\/span><span class=\"p\">()<\/span>\n    <span class=\"n\">status<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">CharField<\/span><span class=\"p\">(<\/span>\n        <span class=\"n\">max_length<\/span><span class=\"o\">=<\/span><span class=\"mi\">10<\/span><span class=\"p\">,<\/span>\n        <span class=\"n\">choices<\/span><span class=\"o\">=<\/span><span class=\"n\">STATUS_CHOICES<\/span><span class=\"p\">,<\/span>\n        <span class=\"n\">default<\/span><span class=\"o\">=<\/span><span class=\"sh\">'<\/span><span class=\"s\">DRAFT<\/span><span class=\"sh\">'<\/span>\n    <span class=\"p\">)<\/span>\n    <span class=\"n\">initial_balance<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">DecimalField<\/span><span class=\"p\">(<\/span>\n        <span class=\"n\">max_digits<\/span><span class=\"o\">=<\/span><span class=\"mi\">15<\/span><span class=\"p\">,<\/span>\n        <span class=\"n\">decimal_places<\/span><span class=\"o\">=<\/span><span class=\"mi\">2<\/span><span class=\"p\">,<\/span>\n        <span class=\"n\">default<\/span><span class=\"o\">=<\/span><span class=\"nc\">Decimal<\/span><span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">0.00<\/span><span class=\"sh\">'<\/span><span class=\"p\">)<\/span>\n    <span class=\"p\">)<\/span>\n    <span class=\"n\">created_at<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">DateTimeField<\/span><span class=\"p\">(<\/span><span class=\"n\">auto_now_add<\/span><span class=\"o\">=<\/span><span class=\"bp\">True<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">closed_at<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">DateTimeField<\/span><span class=\"p\">(<\/span><span class=\"n\">null<\/span><span class=\"o\">=<\/span><span class=\"bp\">True<\/span><span class=\"p\">,<\/span> <span class=\"n\">blank<\/span><span class=\"o\">=<\/span><span class=\"bp\">True<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">closing_notes<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">TextField<\/span><span class=\"p\">(<\/span><span class=\"n\">blank<\/span><span class=\"o\">=<\/span><span class=\"bp\">True<\/span><span class=\"p\">)<\/span>\n\n    <span class=\"k\">class<\/span> <span class=\"nc\">Meta<\/span><span class=\"p\">:<\/span>\n        <span class=\"n\">ordering<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span><span class=\"sh\">'<\/span><span class=\"s\">-start_date<\/span><span class=\"sh\">'<\/span><span class=\"p\">]<\/span>\n        <span class=\"n\">constraints<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span>\n            <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">CheckConstraint<\/span><span class=\"p\">(<\/span>\n                <span class=\"n\">check<\/span><span class=\"o\">=<\/span><span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">Q<\/span><span class=\"p\">(<\/span><span class=\"n\">end_date__gte<\/span><span class=\"o\">=<\/span><span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">F<\/span><span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">start_date<\/span><span class=\"sh\">'<\/span><span class=\"p\">)),<\/span>\n                <span class=\"n\">name<\/span><span class=\"o\">=<\/span><span class=\"sh\">'<\/span><span class=\"s\">valid_period_dates<\/span><span class=\"sh\">'<\/span>\n            <span class=\"p\">)<\/span>\n        <span class=\"p\">]<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">clean<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">_validate_dates<\/span><span class=\"p\">()<\/span>\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">_validate_status_transition<\/span><span class=\"p\">()<\/span>\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">_validate_overlapping<\/span><span class=\"p\">()<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">_validate_dates<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"sh\">\"\"\"<\/span><span class=\"s\">Valida la coherencia de las fechas del per\u00edodo.<\/span><span class=\"sh\">\"\"\"<\/span>\n        <span class=\"k\">if<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">start_date<\/span> <span class=\"ow\">and<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">end_date<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">if<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">start_date<\/span> <span class=\"o\">&gt;<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">end_date<\/span><span class=\"p\">:<\/span>\n                <span class=\"k\">raise<\/span> <span class=\"nc\">ValidationError<\/span><span class=\"p\">(<\/span>\n                    <span class=\"sh\">'<\/span><span class=\"s\">La fecha de inicio debe ser anterior a la fecha de fin.<\/span><span class=\"sh\">'<\/span>\n                <span class=\"p\">)<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">_validate_status_transition<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"sh\">\"\"\"<\/span><span class=\"s\">Valida las transiciones de estado permitidas.<\/span><span class=\"sh\">\"\"\"<\/span>\n        <span class=\"k\">if<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">pk<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">return<\/span>\n\n        <span class=\"n\">old_instance<\/span> <span class=\"o\">=<\/span> <span class=\"n\">AccountingPeriod<\/span><span class=\"p\">.<\/span><span class=\"n\">objects<\/span><span class=\"p\">.<\/span><span class=\"nf\">get<\/span><span class=\"p\">(<\/span><span class=\"n\">pk<\/span><span class=\"o\">=<\/span><span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">pk<\/span><span class=\"p\">)<\/span>\n        <span class=\"n\">valid_transitions<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n            <span class=\"sh\">'<\/span><span class=\"s\">DRAFT<\/span><span class=\"sh\">'<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span><span class=\"sh\">'<\/span><span class=\"s\">ACTIVE<\/span><span class=\"sh\">'<\/span><span class=\"p\">],<\/span>\n            <span class=\"sh\">'<\/span><span class=\"s\">ACTIVE<\/span><span class=\"sh\">'<\/span><span class=\"p\">:<\/span> <span class=\"p\">[<\/span><span class=\"sh\">'<\/span><span class=\"s\">CLOSED<\/span><span class=\"sh\">'<\/span><span class=\"p\">],<\/span>\n            <span class=\"sh\">'<\/span><span class=\"s\">CLOSED<\/span><span class=\"sh\">'<\/span><span class=\"p\">:<\/span> <span class=\"p\">[]<\/span>\n        <span class=\"p\">}<\/span>\n\n        <span class=\"nf\">if <\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"o\">!=<\/span> <span class=\"n\">old_instance<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"ow\">and<\/span> \n            <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"ow\">not<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">valid_transitions<\/span><span class=\"p\">[<\/span><span class=\"n\">old_instance<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span><span class=\"p\">]):<\/span>\n            <span class=\"k\">raise<\/span> <span class=\"nc\">ValidationError<\/span><span class=\"p\">(<\/span>\n                <span class=\"sa\">f<\/span><span class=\"sh\">'<\/span><span class=\"s\">Transici\u00f3n de estado inv\u00e1lida: <\/span><span class=\"si\">{<\/span><span class=\"n\">old_instance<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span><span class=\"si\">}<\/span><span class=\"s\"> -&gt; <\/span><span class=\"si\">{<\/span><span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span><span class=\"si\">}<\/span><span class=\"sh\">'<\/span>\n            <span class=\"p\">)<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">_validate_overlapping<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"sh\">\"\"\"<\/span><span class=\"s\">Previene la superposici\u00f3n de per\u00edodos contables.<\/span><span class=\"sh\">\"\"\"<\/span>\n        <span class=\"n\">overlapping<\/span> <span class=\"o\">=<\/span> <span class=\"n\">AccountingPeriod<\/span><span class=\"p\">.<\/span><span class=\"n\">objects<\/span><span class=\"p\">.<\/span><span class=\"nf\">exclude<\/span><span class=\"p\">(<\/span><span class=\"n\">pk<\/span><span class=\"o\">=<\/span><span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">pk<\/span><span class=\"p\">).<\/span><span class=\"nf\">filter<\/span><span class=\"p\">(<\/span>\n            <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">Q<\/span><span class=\"p\">(<\/span><span class=\"n\">start_date__lte<\/span><span class=\"o\">=<\/span><span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">end_date<\/span><span class=\"p\">)<\/span> <span class=\"o\">&amp;<\/span> \n            <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">Q<\/span><span class=\"p\">(<\/span><span class=\"n\">end_date__gte<\/span><span class=\"o\">=<\/span><span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">start_date<\/span><span class=\"p\">)<\/span>\n        <span class=\"p\">)<\/span>\n        <span class=\"k\">if<\/span> <span class=\"n\">overlapping<\/span><span class=\"p\">.<\/span><span class=\"nf\">exists<\/span><span class=\"p\">():<\/span>\n            <span class=\"k\">raise<\/span> <span class=\"nc\">ValidationError<\/span><span class=\"p\">(<\/span>\n                <span class=\"sh\">'<\/span><span class=\"s\">El per\u00edodo se superpone con per\u00edodos existentes.<\/span><span class=\"sh\">'<\/span>\n            <span class=\"p\">)<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">activate<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"sh\">\"\"\"<\/span><span class=\"s\">Activa un per\u00edodo en estado DRAFT.<\/span><span class=\"sh\">\"\"\"<\/span>\n        <span class=\"k\">if<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"o\">!=<\/span> <span class=\"sh\">'<\/span><span class=\"s\">DRAFT<\/span><span class=\"sh\">'<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">raise<\/span> <span class=\"nc\">ValidationError<\/span><span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">Solo se pueden activar per\u00edodos en borrador.<\/span><span class=\"sh\">'<\/span><span class=\"p\">)<\/span>\n\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"o\">=<\/span> <span class=\"sh\">'<\/span><span class=\"s\">ACTIVE<\/span><span class=\"sh\">'<\/span>\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">save<\/span><span class=\"p\">()<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">can_close<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"sh\">\"\"\"<\/span><span class=\"s\">\n        Verifica si el per\u00edodo puede ser cerrado.\n        Retorna (bool, str) donde bool indica si se puede cerrar\n        y str contiene el mensaje de error si no se puede.\n        <\/span><span class=\"sh\">\"\"\"<\/span>\n        <span class=\"k\">if<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"o\">!=<\/span> <span class=\"sh\">'<\/span><span class=\"s\">ACTIVE<\/span><span class=\"sh\">'<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">return<\/span> <span class=\"bp\">False<\/span><span class=\"p\">,<\/span> <span class=\"sh\">'<\/span><span class=\"s\">Solo se pueden cerrar per\u00edodos activos.<\/span><span class=\"sh\">'<\/span>\n\n        <span class=\"c1\"># Verificar balance\n<\/span>        <span class=\"k\">if<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">is_balanced<\/span><span class=\"p\">():<\/span>\n            <span class=\"k\">return<\/span> <span class=\"bp\">False<\/span><span class=\"p\">,<\/span> <span class=\"sh\">'<\/span><span class=\"s\">El per\u00edodo no est\u00e1 balanceado.<\/span><span class=\"sh\">'<\/span>\n\n        <span class=\"c1\"># Verificar entradas sin procesar\n<\/span>        <span class=\"k\">if<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">has_pending_entries<\/span><span class=\"p\">():<\/span>\n            <span class=\"k\">return<\/span> <span class=\"bp\">False<\/span><span class=\"p\">,<\/span> <span class=\"sh\">'<\/span><span class=\"s\">Existen entradas pendientes de procesar.<\/span><span class=\"sh\">'<\/span>\n\n        <span class=\"k\">return<\/span> <span class=\"bp\">True<\/span><span class=\"p\">,<\/span> <span class=\"sh\">''<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">close<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">,<\/span> <span class=\"n\">closing_notes<\/span><span class=\"o\">=<\/span><span class=\"sh\">''<\/span><span class=\"p\">):<\/span>\n        <span class=\"sh\">\"\"\"<\/span><span class=\"s\">\n        Cierra el per\u00edodo si todas las validaciones pasan.\n        <\/span><span class=\"sh\">\"\"\"<\/span>\n        <span class=\"n\">can_close<\/span><span class=\"p\">,<\/span> <span class=\"n\">message<\/span> <span class=\"o\">=<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">can_close<\/span><span class=\"p\">()<\/span>\n        <span class=\"k\">if<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">can_close<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">raise<\/span> <span class=\"nc\">ValidationError<\/span><span class=\"p\">(<\/span><span class=\"n\">message<\/span><span class=\"p\">)<\/span>\n\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"o\">=<\/span> <span class=\"sh\">'<\/span><span class=\"s\">CLOSED<\/span><span class=\"sh\">'<\/span>\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">closed_at<\/span> <span class=\"o\">=<\/span> <span class=\"n\">timezone<\/span><span class=\"p\">.<\/span><span class=\"nf\">now<\/span><span class=\"p\">()<\/span>\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">closing_notes<\/span> <span class=\"o\">=<\/span> <span class=\"n\">closing_notes<\/span>\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">save<\/span><span class=\"p\">()<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">is_balanced<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"sh\">\"\"\"<\/span><span class=\"s\">\n        Verifica si los d\u00e9bitos y cr\u00e9ditos est\u00e1n balanceados.\n        <\/span><span class=\"sh\">\"\"\"<\/span>\n        <span class=\"n\">totals<\/span> <span class=\"o\">=<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">entries<\/span><span class=\"p\">.<\/span><span class=\"nf\">aggregate<\/span><span class=\"p\">(<\/span>\n            <span class=\"n\">total_debit<\/span><span class=\"o\">=<\/span><span class=\"nc\">Sum<\/span><span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">debit_amount<\/span><span class=\"sh\">'<\/span><span class=\"p\">),<\/span>\n            <span class=\"n\">total_credit<\/span><span class=\"o\">=<\/span><span class=\"nc\">Sum<\/span><span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">credit_amount<\/span><span class=\"sh\">'<\/span><span class=\"p\">)<\/span>\n        <span class=\"p\">)<\/span>\n\n        <span class=\"n\">total_debit<\/span> <span class=\"o\">=<\/span> <span class=\"n\">totals<\/span><span class=\"p\">[<\/span><span class=\"sh\">'<\/span><span class=\"s\">total_debit<\/span><span class=\"sh\">'<\/span><span class=\"p\">]<\/span> <span class=\"ow\">or<\/span> <span class=\"nc\">Decimal<\/span><span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">0.00<\/span><span class=\"sh\">'<\/span><span class=\"p\">)<\/span>\n        <span class=\"n\">total_credit<\/span> <span class=\"o\">=<\/span> <span class=\"n\">totals<\/span><span class=\"p\">[<\/span><span class=\"sh\">'<\/span><span class=\"s\">total_credit<\/span><span class=\"sh\">'<\/span><span class=\"p\">]<\/span> <span class=\"ow\">or<\/span> <span class=\"nc\">Decimal<\/span><span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">0.00<\/span><span class=\"sh\">'<\/span><span class=\"p\">)<\/span>\n\n        <span class=\"k\">return<\/span> <span class=\"nf\">abs<\/span><span class=\"p\">(<\/span><span class=\"n\">total_debit<\/span> <span class=\"o\">-<\/span> <span class=\"n\">total_credit<\/span><span class=\"p\">)<\/span> <span class=\"o\">&lt;<\/span> <span class=\"nc\">Decimal<\/span><span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">0.01<\/span><span class=\"sh\">'<\/span><span class=\"p\">)<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">has_pending_entries<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"sh\">\"\"\"<\/span><span class=\"s\">\n        Verifica si hay entradas pendientes de procesar.\n        <\/span><span class=\"sh\">\"\"\"<\/span>\n        <span class=\"k\">return<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">entries<\/span><span class=\"p\">.<\/span><span class=\"nf\">filter<\/span><span class=\"p\">(<\/span><span class=\"n\">status<\/span><span class=\"o\">=<\/span><span class=\"sh\">'<\/span><span class=\"s\">PENDING<\/span><span class=\"sh\">'<\/span><span class=\"p\">).<\/span><span class=\"nf\">exists<\/span><span class=\"p\">()<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">__str__<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"k\">return<\/span> <span class=\"sa\">f<\/span><span class=\"sh\">\"<\/span><span class=\"si\">{<\/span><span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">name<\/span><span class=\"si\">}<\/span><span class=\"s\"> (<\/span><span class=\"si\">{<\/span><span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">get_status_display<\/span><span class=\"p\">()<\/span><span class=\"si\">}<\/span><span class=\"s\">)<\/span><span class=\"sh\">\"<\/span>\n\n\n<span class=\"k\">class<\/span> <span class=\"nc\">AccountingEntry<\/span><span class=\"p\">(<\/span><span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">Model<\/span><span class=\"p\">):<\/span>\n    <span class=\"sh\">\"\"\"<\/span><span class=\"s\">\n    Representa una entrada contable individual dentro de un per\u00edodo.\n    <\/span><span class=\"sh\">\"\"\"<\/span>\n    <span class=\"n\">STATUS_CHOICES<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span>\n        <span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">PENDING<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> <span class=\"sh\">'<\/span><span class=\"s\">Pendiente<\/span><span class=\"sh\">'<\/span><span class=\"p\">),<\/span>\n        <span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">PROCESSED<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> <span class=\"sh\">'<\/span><span class=\"s\">Procesado<\/span><span class=\"sh\">'<\/span><span class=\"p\">),<\/span>\n        <span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">REJECTED<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> <span class=\"sh\">'<\/span><span class=\"s\">Rechazado<\/span><span class=\"sh\">'<\/span><span class=\"p\">),<\/span>\n    <span class=\"p\">]<\/span>\n\n    <span class=\"n\">period<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">ForeignKey<\/span><span class=\"p\">(<\/span>\n        <span class=\"n\">AccountingPeriod<\/span><span class=\"p\">,<\/span>\n        <span class=\"n\">on_delete<\/span><span class=\"o\">=<\/span><span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"n\">PROTECT<\/span><span class=\"p\">,<\/span>\n        <span class=\"n\">related_name<\/span><span class=\"o\">=<\/span><span class=\"sh\">'<\/span><span class=\"s\">entries<\/span><span class=\"sh\">'<\/span>\n    <span class=\"p\">)<\/span>\n    <span class=\"n\">date<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">DateField<\/span><span class=\"p\">()<\/span>\n    <span class=\"n\">description<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">CharField<\/span><span class=\"p\">(<\/span><span class=\"n\">max_length<\/span><span class=\"o\">=<\/span><span class=\"mi\">200<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">debit_amount<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">DecimalField<\/span><span class=\"p\">(<\/span>\n        <span class=\"n\">max_digits<\/span><span class=\"o\">=<\/span><span class=\"mi\">15<\/span><span class=\"p\">,<\/span>\n        <span class=\"n\">decimal_places<\/span><span class=\"o\">=<\/span><span class=\"mi\">2<\/span><span class=\"p\">,<\/span>\n        <span class=\"n\">default<\/span><span class=\"o\">=<\/span><span class=\"nc\">Decimal<\/span><span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">0.00<\/span><span class=\"sh\">'<\/span><span class=\"p\">)<\/span>\n    <span class=\"p\">)<\/span>\n    <span class=\"n\">credit_amount<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">DecimalField<\/span><span class=\"p\">(<\/span>\n        <span class=\"n\">max_digits<\/span><span class=\"o\">=<\/span><span class=\"mi\">15<\/span><span class=\"p\">,<\/span>\n        <span class=\"n\">decimal_places<\/span><span class=\"o\">=<\/span><span class=\"mi\">2<\/span><span class=\"p\">,<\/span>\n        <span class=\"n\">default<\/span><span class=\"o\">=<\/span><span class=\"nc\">Decimal<\/span><span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">0.00<\/span><span class=\"sh\">'<\/span><span class=\"p\">)<\/span>\n    <span class=\"p\">)<\/span>\n    <span class=\"n\">status<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">CharField<\/span><span class=\"p\">(<\/span>\n        <span class=\"n\">max_length<\/span><span class=\"o\">=<\/span><span class=\"mi\">10<\/span><span class=\"p\">,<\/span>\n        <span class=\"n\">choices<\/span><span class=\"o\">=<\/span><span class=\"n\">STATUS_CHOICES<\/span><span class=\"p\">,<\/span>\n        <span class=\"n\">default<\/span><span class=\"o\">=<\/span><span class=\"sh\">'<\/span><span class=\"s\">PENDING<\/span><span class=\"sh\">'<\/span>\n    <span class=\"p\">)<\/span>\n    <span class=\"n\">created_at<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">DateTimeField<\/span><span class=\"p\">(<\/span><span class=\"n\">auto_now_add<\/span><span class=\"o\">=<\/span><span class=\"bp\">True<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">processed_at<\/span> <span class=\"o\">=<\/span> <span class=\"n\">models<\/span><span class=\"p\">.<\/span><span class=\"nc\">DateTimeField<\/span><span class=\"p\">(<\/span><span class=\"n\">null<\/span><span class=\"o\">=<\/span><span class=\"bp\">True<\/span><span class=\"p\">,<\/span> <span class=\"n\">blank<\/span><span class=\"o\">=<\/span><span class=\"bp\">True<\/span><span class=\"p\">)<\/span>\n\n    <span class=\"k\">class<\/span> <span class=\"nc\">Meta<\/span><span class=\"p\">:<\/span>\n        <span class=\"n\">ordering<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span><span class=\"sh\">'<\/span><span class=\"s\">-date<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> <span class=\"sh\">'<\/span><span class=\"s\">-created_at<\/span><span class=\"sh\">'<\/span><span class=\"p\">]<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">clean<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">_validate_period<\/span><span class=\"p\">()<\/span>\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">_validate_amounts<\/span><span class=\"p\">()<\/span>\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">_validate_date<\/span><span class=\"p\">()<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">_validate_period<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"sh\">\"\"\"<\/span><span class=\"s\">Valida que el per\u00edodo est\u00e9 activo y la entrada pueda ser modificada.<\/span><span class=\"sh\">\"\"\"<\/span>\n        <span class=\"k\">if<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">period_id<\/span><span class=\"p\">:<\/span>\n            <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">period<\/span> <span class=\"o\">=<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">_get_appropriate_period<\/span><span class=\"p\">()<\/span>\n\n        <span class=\"k\">if<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">period<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"o\">==<\/span> <span class=\"sh\">'<\/span><span class=\"s\">CLOSED<\/span><span class=\"sh\">'<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">raise<\/span> <span class=\"nc\">ValidationError<\/span><span class=\"p\">(<\/span>\n                <span class=\"sh\">'<\/span><span class=\"s\">No se pueden crear o modificar entradas en per\u00edodos cerrados.<\/span><span class=\"sh\">'<\/span>\n            <span class=\"p\">)<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">_validate_amounts<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"sh\">\"\"\"<\/span><span class=\"s\">Valida que los montos sean coherentes.<\/span><span class=\"sh\">\"\"\"<\/span>\n        <span class=\"k\">if<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">debit_amount<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span> <span class=\"ow\">or<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">credit_amount<\/span> <span class=\"o\">&lt;<\/span> <span class=\"mi\">0<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">raise<\/span> <span class=\"nc\">ValidationError<\/span><span class=\"p\">(<\/span>\n                <span class=\"sh\">'<\/span><span class=\"s\">Los montos no pueden ser negativos.<\/span><span class=\"sh\">'<\/span>\n            <span class=\"p\">)<\/span>\n\n        <span class=\"k\">if<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">debit_amount<\/span> <span class=\"o\">==<\/span> <span class=\"mi\">0<\/span> <span class=\"ow\">and<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">credit_amount<\/span> <span class=\"o\">==<\/span> <span class=\"mi\">0<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">raise<\/span> <span class=\"nc\">ValidationError<\/span><span class=\"p\">(<\/span>\n                <span class=\"sh\">'<\/span><span class=\"s\">Al menos un monto debe ser mayor a cero.<\/span><span class=\"sh\">'<\/span>\n            <span class=\"p\">)<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">_validate_date<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"sh\">\"\"\"<\/span><span class=\"s\">Valida que la fecha est\u00e9 dentro del per\u00edodo.<\/span><span class=\"sh\">\"\"\"<\/span>\n        <span class=\"k\">if<\/span> <span class=\"ow\">not<\/span> <span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">period<\/span><span class=\"p\">.<\/span><span class=\"n\">start_date<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">date<\/span> <span class=\"o\">&lt;=<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">period<\/span><span class=\"p\">.<\/span><span class=\"n\">end_date<\/span><span class=\"p\">):<\/span>\n            <span class=\"k\">raise<\/span> <span class=\"nc\">ValidationError<\/span><span class=\"p\">(<\/span>\n                <span class=\"sh\">'<\/span><span class=\"s\">La fecha debe estar dentro del per\u00edodo contable.<\/span><span class=\"sh\">'<\/span>\n            <span class=\"p\">)<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">_get_appropriate_period<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"sh\">\"\"\"<\/span><span class=\"s\">Obtiene el per\u00edodo activo adecuado para la fecha de la entrada.<\/span><span class=\"sh\">\"\"\"<\/span>\n        <span class=\"n\">period<\/span> <span class=\"o\">=<\/span> <span class=\"n\">AccountingPeriod<\/span><span class=\"p\">.<\/span><span class=\"n\">objects<\/span><span class=\"p\">.<\/span><span class=\"nf\">filter<\/span><span class=\"p\">(<\/span>\n            <span class=\"n\">start_date__lte<\/span><span class=\"o\">=<\/span><span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">date<\/span><span class=\"p\">,<\/span>\n            <span class=\"n\">end_date__gte<\/span><span class=\"o\">=<\/span><span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">date<\/span><span class=\"p\">,<\/span>\n            <span class=\"n\">status<\/span><span class=\"o\">=<\/span><span class=\"sh\">'<\/span><span class=\"s\">ACTIVE<\/span><span class=\"sh\">'<\/span>\n        <span class=\"p\">).<\/span><span class=\"nf\">first<\/span><span class=\"p\">()<\/span>\n\n        <span class=\"k\">if<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">period<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">raise<\/span> <span class=\"nc\">ValidationError<\/span><span class=\"p\">(<\/span>\n                <span class=\"sh\">'<\/span><span class=\"s\">No existe un per\u00edodo activo para la fecha especificada.<\/span><span class=\"sh\">'<\/span>\n            <span class=\"p\">)<\/span>\n        <span class=\"k\">return<\/span> <span class=\"n\">period<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">process<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"sh\">\"\"\"<\/span><span class=\"s\">Procesa la entrada contable.<\/span><span class=\"sh\">\"\"\"<\/span>\n        <span class=\"k\">if<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"o\">!=<\/span> <span class=\"sh\">'<\/span><span class=\"s\">PENDING<\/span><span class=\"sh\">'<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">raise<\/span> <span class=\"nc\">ValidationError<\/span><span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">Solo se pueden procesar entradas pendientes.<\/span><span class=\"sh\">'<\/span><span class=\"p\">)<\/span>\n\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"o\">=<\/span> <span class=\"sh\">'<\/span><span class=\"s\">PROCESSED<\/span><span class=\"sh\">'<\/span>\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">processed_at<\/span> <span class=\"o\">=<\/span> <span class=\"n\">timezone<\/span><span class=\"p\">.<\/span><span class=\"nf\">now<\/span><span class=\"p\">()<\/span>\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">save<\/span><span class=\"p\">()<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">reject<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"sh\">\"\"\"<\/span><span class=\"s\">Rechaza la entrada contable.<\/span><span class=\"sh\">\"\"\"<\/span>\n        <span class=\"k\">if<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"o\">!=<\/span> <span class=\"sh\">'<\/span><span class=\"s\">PENDING<\/span><span class=\"sh\">'<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">raise<\/span> <span class=\"nc\">ValidationError<\/span><span class=\"p\">(<\/span><span class=\"sh\">'<\/span><span class=\"s\">Solo se pueden rechazar entradas pendientes.<\/span><span class=\"sh\">'<\/span><span class=\"p\">)<\/span>\n\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"o\">=<\/span> <span class=\"sh\">'<\/span><span class=\"s\">REJECTED<\/span><span class=\"sh\">'<\/span>\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">processed_at<\/span> <span class=\"o\">=<\/span> <span class=\"n\">timezone<\/span><span class=\"p\">.<\/span><span class=\"nf\">now<\/span><span class=\"p\">()<\/span>\n        <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">save<\/span><span class=\"p\">()<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">__str__<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">):<\/span>\n        <span class=\"k\">return<\/span> <span class=\"sa\">f<\/span><span class=\"sh\">\"<\/span><span class=\"si\">{<\/span><span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">date<\/span><span class=\"si\">}<\/span><span class=\"s\"> - <\/span><span class=\"si\">{<\/span><span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">description<\/span><span class=\"si\">}<\/span><span class=\"sh\">\"<\/span>\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<h3><span class=\"ez-toc-section\" id=\"%D8%AA%D9%86%D8%B8%DB%8C%D9%85%D8%A7%D8%AA_%D9%85%D8%AF%DB%8C%D8%B1%DB%8C%D8%AA\"><\/span>\n<p>  \u062a\u0646\u0638\u06cc\u0645\u0627\u062a \u0645\u062f\u06cc\u0631\u06cc\u062a<br \/>\n<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight python\"><code><span class=\"kn\">from<\/span> <span class=\"n\">django.contrib<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">admin<\/span>\n<span class=\"kn\">from<\/span> <span class=\"n\">django.utils.html<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">format_html<\/span>\n<span class=\"kn\">from<\/span> <span class=\"n\">django.contrib<\/span> <span class=\"kn\">import<\/span> <span class=\"n\">messages<\/span>\n\n<span class=\"nd\">@admin.register<\/span><span class=\"p\">(<\/span><span class=\"n\">AccountingPeriod<\/span><span class=\"p\">)<\/span>\n<span class=\"k\">class<\/span> <span class=\"nc\">AccountingPeriodAdmin<\/span><span class=\"p\">(<\/span><span class=\"n\">admin<\/span><span class=\"p\">.<\/span><span class=\"n\">ModelAdmin<\/span><span class=\"p\">):<\/span>\n    <span class=\"n\">list_display<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span>\n        <span class=\"sh\">'<\/span><span class=\"s\">name<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> \n        <span class=\"sh\">'<\/span><span class=\"s\">start_date<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> \n        <span class=\"sh\">'<\/span><span class=\"s\">end_date<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> \n        <span class=\"sh\">'<\/span><span class=\"s\">status_display<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> \n        <span class=\"sh\">'<\/span><span class=\"s\">balance_display<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"sh\">'<\/span><span class=\"s\">created_at<\/span><span class=\"sh\">'<\/span>\n    <span class=\"p\">]<\/span>\n    <span class=\"n\">list_filter<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span><span class=\"sh\">'<\/span><span class=\"s\">status<\/span><span class=\"sh\">'<\/span><span class=\"p\">]<\/span>\n    <span class=\"n\">search_fields<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span><span class=\"sh\">'<\/span><span class=\"s\">name<\/span><span class=\"sh\">'<\/span><span class=\"p\">]<\/span>\n    <span class=\"n\">readonly_fields<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span><span class=\"sh\">'<\/span><span class=\"s\">closed_at<\/span><span class=\"sh\">'<\/span><span class=\"p\">]<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">status_display<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">,<\/span> <span class=\"n\">obj<\/span><span class=\"p\">):<\/span>\n        <span class=\"n\">colors<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n            <span class=\"sh\">'<\/span><span class=\"s\">DRAFT<\/span><span class=\"sh\">'<\/span><span class=\"p\">:<\/span> <span class=\"sh\">'<\/span><span class=\"s\">#6c757d<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span>\n            <span class=\"sh\">'<\/span><span class=\"s\">ACTIVE<\/span><span class=\"sh\">'<\/span><span class=\"p\">:<\/span> <span class=\"sh\">'<\/span><span class=\"s\">#28a745<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span>\n            <span class=\"sh\">'<\/span><span class=\"s\">CLOSED<\/span><span class=\"sh\">'<\/span><span class=\"p\">:<\/span> <span class=\"sh\">'<\/span><span class=\"s\">#dc3545<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"p\">}<\/span>\n        <span class=\"k\">return<\/span> <span class=\"nf\">format_html<\/span><span class=\"p\">(<\/span>\n            <span class=\"sh\">'<\/span><span class=\"s\"><span style=\"&lt;\/span\"><span class=\"sh\">\"<\/span><span class=\"s\">color: {}; font-weight: bold;<\/span><span class=\"sh\">\"<\/span><span class=\"s\">&gt;{}<\/span><\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span>\n            <span class=\"n\">colors<\/span><span class=\"p\">[<\/span><span class=\"n\">obj<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span><span class=\"p\">],<\/span>\n            <span class=\"n\">obj<\/span><span class=\"p\">.<\/span><span class=\"nf\">get_status_display<\/span><span class=\"p\">()<\/span>\n        <span class=\"p\">)<\/span>\n    <span class=\"n\">status_display<\/span><span class=\"p\">.<\/span><span class=\"n\">short_description<\/span> <span class=\"o\">=<\/span> <span class=\"sh\">'<\/span><span class=\"s\">Estado<\/span><span class=\"sh\">'<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">balance_display<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">,<\/span> <span class=\"n\">obj<\/span><span class=\"p\">):<\/span>\n        <span class=\"k\">if<\/span> <span class=\"n\">obj<\/span><span class=\"p\">.<\/span><span class=\"nf\">is_balanced<\/span><span class=\"p\">():<\/span>\n            <span class=\"k\">return<\/span> <span class=\"nf\">format_html<\/span><span class=\"p\">(<\/span>\n                <span class=\"sh\">'<\/span><span class=\"s\"><span style=\"&lt;\/span\"><span class=\"sh\">\"<\/span><span class=\"s\">color: #28a745;<\/span><span class=\"sh\">\"<\/span><span class=\"s\">&gt;Balanceado<\/span><\/span><span class=\"sh\">'<\/span>\n            <span class=\"p\">)<\/span>\n        <span class=\"k\">return<\/span> <span class=\"nf\">format_html<\/span><span class=\"p\">(<\/span>\n            <span class=\"sh\">'<\/span><span class=\"s\"><span style=\"&lt;\/span\"><span class=\"sh\">\"<\/span><span class=\"s\">color: #dc3545;<\/span><span class=\"sh\">\"<\/span><span class=\"s\">&gt;Desbalanceado<\/span><\/span><span class=\"sh\">'<\/span>\n        <span class=\"p\">)<\/span>\n    <span class=\"n\">balance_display<\/span><span class=\"p\">.<\/span><span class=\"n\">short_description<\/span> <span class=\"o\">=<\/span> <span class=\"sh\">'<\/span><span class=\"s\">Balance<\/span><span class=\"sh\">'<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">get_readonly_fields<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">,<\/span> <span class=\"n\">request<\/span><span class=\"p\">,<\/span> <span class=\"n\">obj<\/span><span class=\"o\">=<\/span><span class=\"bp\">None<\/span><span class=\"p\">):<\/span>\n        <span class=\"k\">if<\/span> <span class=\"n\">obj<\/span> <span class=\"ow\">and<\/span> <span class=\"n\">obj<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"o\">==<\/span> <span class=\"sh\">'<\/span><span class=\"s\">CLOSED<\/span><span class=\"sh\">'<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">return<\/span> <span class=\"p\">[<\/span><span class=\"n\">f<\/span><span class=\"p\">.<\/span><span class=\"n\">name<\/span> <span class=\"k\">for<\/span> <span class=\"n\">f<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">obj<\/span><span class=\"p\">.<\/span><span class=\"n\">_meta<\/span><span class=\"p\">.<\/span><span class=\"n\">fields<\/span><span class=\"p\">]<\/span>\n        <span class=\"k\">return<\/span> <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"n\">readonly_fields<\/span>\n\n    <span class=\"n\">actions<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span><span class=\"sh\">'<\/span><span class=\"s\">activate_periods<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> <span class=\"sh\">'<\/span><span class=\"s\">close_periods<\/span><span class=\"sh\">'<\/span><span class=\"p\">]<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">activate_periods<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">,<\/span> <span class=\"n\">request<\/span><span class=\"p\">,<\/span> <span class=\"n\">queryset<\/span><span class=\"p\">):<\/span>\n        <span class=\"k\">for<\/span> <span class=\"n\">period<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">queryset<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">try<\/span><span class=\"p\">:<\/span>\n                <span class=\"n\">period<\/span><span class=\"p\">.<\/span><span class=\"nf\">activate<\/span><span class=\"p\">()<\/span>\n                <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">message_user<\/span><span class=\"p\">(<\/span>\n                    <span class=\"n\">request<\/span><span class=\"p\">,<\/span>\n                    <span class=\"sa\">f<\/span><span class=\"sh\">'<\/span><span class=\"s\">Per\u00edodo <\/span><span class=\"si\">{<\/span><span class=\"n\">period<\/span><span class=\"p\">.<\/span><span class=\"n\">name<\/span><span class=\"si\">}<\/span><span class=\"s\"> activado exitosamente.<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span>\n                    <span class=\"n\">messages<\/span><span class=\"p\">.<\/span><span class=\"n\">SUCCESS<\/span>\n                <span class=\"p\">)<\/span>\n            <span class=\"k\">except<\/span> <span class=\"n\">ValidationError<\/span> <span class=\"k\">as<\/span> <span class=\"n\">e<\/span><span class=\"p\">:<\/span>\n                <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">message_user<\/span><span class=\"p\">(<\/span>\n                    <span class=\"n\">request<\/span><span class=\"p\">,<\/span>\n                    <span class=\"sa\">f<\/span><span class=\"sh\">\"<\/span><span class=\"s\">Error al activar <\/span><span class=\"si\">{<\/span><span class=\"n\">period<\/span><span class=\"p\">.<\/span><span class=\"n\">name<\/span><span class=\"si\">}<\/span><span class=\"s\">: <\/span><span class=\"si\">{<\/span><span class=\"nf\">str<\/span><span class=\"p\">(<\/span><span class=\"n\">e<\/span><span class=\"p\">)<\/span><span class=\"si\">}<\/span><span class=\"sh\">\"<\/span><span class=\"p\">,<\/span>\n                    <span class=\"n\">messages<\/span><span class=\"p\">.<\/span><span class=\"n\">ERROR<\/span>\n                <span class=\"p\">)<\/span>\n    <span class=\"n\">activate_periods<\/span><span class=\"p\">.<\/span><span class=\"n\">short_description<\/span> <span class=\"o\">=<\/span> <span class=\"sh\">\"<\/span><span class=\"s\">Activar per\u00edodos seleccionados<\/span><span class=\"sh\">\"<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">close_periods<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">,<\/span> <span class=\"n\">request<\/span><span class=\"p\">,<\/span> <span class=\"n\">queryset<\/span><span class=\"p\">):<\/span>\n        <span class=\"k\">for<\/span> <span class=\"n\">period<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">queryset<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">try<\/span><span class=\"p\">:<\/span>\n                <span class=\"n\">period<\/span><span class=\"p\">.<\/span><span class=\"nf\">close<\/span><span class=\"p\">()<\/span>\n                <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">message_user<\/span><span class=\"p\">(<\/span>\n                    <span class=\"n\">request<\/span><span class=\"p\">,<\/span>\n                    <span class=\"sa\">f<\/span><span class=\"sh\">'<\/span><span class=\"s\">Per\u00edodo <\/span><span class=\"si\">{<\/span><span class=\"n\">period<\/span><span class=\"p\">.<\/span><span class=\"n\">name<\/span><span class=\"si\">}<\/span><span class=\"s\"> cerrado exitosamente.<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span>\n                    <span class=\"n\">messages<\/span><span class=\"p\">.<\/span><span class=\"n\">SUCCESS<\/span>\n                <span class=\"p\">)<\/span>\n            <span class=\"k\">except<\/span> <span class=\"n\">ValidationError<\/span> <span class=\"k\">as<\/span> <span class=\"n\">e<\/span><span class=\"p\">:<\/span>\n                <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">message_user<\/span><span class=\"p\">(<\/span>\n                    <span class=\"n\">request<\/span><span class=\"p\">,<\/span>\n                    <span class=\"sa\">f<\/span><span class=\"sh\">\"<\/span><span class=\"s\">Error al cerrar <\/span><span class=\"si\">{<\/span><span class=\"n\">period<\/span><span class=\"p\">.<\/span><span class=\"n\">name<\/span><span class=\"si\">}<\/span><span class=\"s\">: <\/span><span class=\"si\">{<\/span><span class=\"nf\">str<\/span><span class=\"p\">(<\/span><span class=\"n\">e<\/span><span class=\"p\">)<\/span><span class=\"si\">}<\/span><span class=\"sh\">\"<\/span><span class=\"p\">,<\/span>\n                    <span class=\"n\">messages<\/span><span class=\"p\">.<\/span><span class=\"n\">ERROR<\/span>\n                <span class=\"p\">)<\/span>\n    <span class=\"n\">close_periods<\/span><span class=\"p\">.<\/span><span class=\"n\">short_description<\/span> <span class=\"o\">=<\/span> <span class=\"sh\">\"<\/span><span class=\"s\">Cerrar per\u00edodos seleccionados<\/span><span class=\"sh\">\"<\/span>\n\n<span class=\"nd\">@admin.register<\/span><span class=\"p\">(<\/span><span class=\"n\">AccountingEntry<\/span><span class=\"p\">)<\/span>\n<span class=\"k\">class<\/span> <span class=\"nc\">AccountingEntryAdmin<\/span><span class=\"p\">(<\/span><span class=\"n\">admin<\/span><span class=\"p\">.<\/span><span class=\"n\">ModelAdmin<\/span><span class=\"p\">):<\/span>\n    <span class=\"n\">list_display<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span>\n        <span class=\"sh\">'<\/span><span class=\"s\">date<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> \n        <span class=\"sh\">'<\/span><span class=\"s\">description<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> \n        <span class=\"sh\">'<\/span><span class=\"s\">debit_amount<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> \n        <span class=\"sh\">'<\/span><span class=\"s\">credit_amount<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"sh\">'<\/span><span class=\"s\">status_display<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> \n        <span class=\"sh\">'<\/span><span class=\"s\">period<\/span><span class=\"sh\">'<\/span>\n    <span class=\"p\">]<\/span>\n    <span class=\"n\">list_filter<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span><span class=\"sh\">'<\/span><span class=\"s\">period<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> <span class=\"sh\">'<\/span><span class=\"s\">status<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> <span class=\"sh\">'<\/span><span class=\"s\">date<\/span><span class=\"sh\">'<\/span><span class=\"p\">]<\/span>\n    <span class=\"n\">search_fields<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span><span class=\"sh\">'<\/span><span class=\"s\">description<\/span><span class=\"sh\">'<\/span><span class=\"p\">]<\/span>\n    <span class=\"n\">actions<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span><span class=\"sh\">'<\/span><span class=\"s\">process_entries<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span> <span class=\"sh\">'<\/span><span class=\"s\">reject_entries<\/span><span class=\"sh\">'<\/span><span class=\"p\">]<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">status_display<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">,<\/span> <span class=\"n\">obj<\/span><span class=\"p\">):<\/span>\n        <span class=\"n\">colors<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n            <span class=\"sh\">'<\/span><span class=\"s\">PENDING<\/span><span class=\"sh\">'<\/span><span class=\"p\">:<\/span> <span class=\"sh\">'<\/span><span class=\"s\">#ffc107<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span>\n            <span class=\"sh\">'<\/span><span class=\"s\">PROCESSED<\/span><span class=\"sh\">'<\/span><span class=\"p\">:<\/span> <span class=\"sh\">'<\/span><span class=\"s\">#28a745<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span>\n            <span class=\"sh\">'<\/span><span class=\"s\">REJECTED<\/span><span class=\"sh\">'<\/span><span class=\"p\">:<\/span> <span class=\"sh\">'<\/span><span class=\"s\">#dc3545<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span>\n        <span class=\"p\">}<\/span>\n        <span class=\"k\">return<\/span> <span class=\"nf\">format_html<\/span><span class=\"p\">(<\/span>\n            <span class=\"sh\">'<\/span><span class=\"s\"><span style=\"&lt;\/span\"><span class=\"sh\">\"<\/span><span class=\"s\">color: {}; font-weight: bold;<\/span><span class=\"sh\">\"<\/span><span class=\"s\">&gt;{}<\/span><\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span>\n            <span class=\"n\">colors<\/span><span class=\"p\">[<\/span><span class=\"n\">obj<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span><span class=\"p\">],<\/span>\n            <span class=\"n\">obj<\/span><span class=\"p\">.<\/span><span class=\"nf\">get_status_display<\/span><span class=\"p\">()<\/span>\n        <span class=\"p\">)<\/span>\n    <span class=\"n\">status_display<\/span><span class=\"p\">.<\/span><span class=\"n\">short_description<\/span> <span class=\"o\">=<\/span> <span class=\"sh\">'<\/span><span class=\"s\">Estado<\/span><span class=\"sh\">'<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">get_readonly_fields<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">,<\/span> <span class=\"n\">request<\/span><span class=\"p\">,<\/span> <span class=\"n\">obj<\/span><span class=\"o\">=<\/span><span class=\"bp\">None<\/span><span class=\"p\">):<\/span>\n        <span class=\"k\">if<\/span> <span class=\"n\">obj<\/span> <span class=\"ow\">and<\/span> <span class=\"p\">(<\/span><span class=\"n\">obj<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"o\">!=<\/span> <span class=\"sh\">'<\/span><span class=\"s\">PENDING<\/span><span class=\"sh\">'<\/span> <span class=\"ow\">or<\/span> <span class=\"n\">obj<\/span><span class=\"p\">.<\/span><span class=\"n\">period<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"o\">==<\/span> <span class=\"sh\">'<\/span><span class=\"s\">CLOSED<\/span><span class=\"sh\">'<\/span><span class=\"p\">):<\/span>\n            <span class=\"k\">return<\/span> <span class=\"p\">[<\/span><span class=\"n\">f<\/span><span class=\"p\">.<\/span><span class=\"n\">name<\/span> <span class=\"k\">for<\/span> <span class=\"n\">f<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">obj<\/span><span class=\"p\">.<\/span><span class=\"n\">_meta<\/span><span class=\"p\">.<\/span><span class=\"n\">fields<\/span><span class=\"p\">]<\/span>\n        <span class=\"k\">return<\/span> <span class=\"p\">[]<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">has_delete_permission<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">,<\/span> <span class=\"n\">request<\/span><span class=\"p\">,<\/span> <span class=\"n\">obj<\/span><span class=\"o\">=<\/span><span class=\"bp\">None<\/span><span class=\"p\">):<\/span>\n        <span class=\"k\">if<\/span> <span class=\"n\">obj<\/span> <span class=\"ow\">and<\/span> <span class=\"p\">(<\/span><span class=\"n\">obj<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"o\">!=<\/span> <span class=\"sh\">'<\/span><span class=\"s\">PENDING<\/span><span class=\"sh\">'<\/span> <span class=\"ow\">or<\/span> <span class=\"n\">obj<\/span><span class=\"p\">.<\/span><span class=\"n\">period<\/span><span class=\"p\">.<\/span><span class=\"n\">status<\/span> <span class=\"o\">==<\/span> <span class=\"sh\">'<\/span><span class=\"s\">CLOSED<\/span><span class=\"sh\">'<\/span><span class=\"p\">):<\/span>\n            <span class=\"k\">return<\/span> <span class=\"bp\">False<\/span>\n        <span class=\"k\">return<\/span> <span class=\"nf\">super<\/span><span class=\"p\">().<\/span><span class=\"nf\">has_delete_permission<\/span><span class=\"p\">(<\/span><span class=\"n\">request<\/span><span class=\"p\">,<\/span> <span class=\"n\">obj<\/span><span class=\"p\">)<\/span>\n\n    <span class=\"k\">def<\/span> <span class=\"nf\">process_entries<\/span><span class=\"p\">(<\/span><span class=\"n\">self<\/span><span class=\"p\">,<\/span> <span class=\"n\">request<\/span><span class=\"p\">,<\/span> <span class=\"n\">queryset<\/span><span class=\"p\">):<\/span>\n        <span class=\"k\">for<\/span> <span class=\"n\">entry<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">queryset<\/span><span class=\"p\">:<\/span>\n            <span class=\"k\">try<\/span><span class=\"p\">:<\/span>\n                <span class=\"n\">entry<\/span><span class=\"p\">.<\/span><span class=\"nf\">process<\/span><span class=\"p\">()<\/span>\n                <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">message_user<\/span><span class=\"p\">(<\/span>\n                    <span class=\"n\">request<\/span><span class=\"p\">,<\/span>\n                    <span class=\"sa\">f<\/span><span class=\"sh\">'<\/span><span class=\"s\">Entrada <\/span><span class=\"si\">{<\/span><span class=\"n\">entry<\/span><span class=\"si\">}<\/span><span class=\"s\"> procesada exitosamente.<\/span><span class=\"sh\">'<\/span><span class=\"p\">,<\/span>\n                    <span class=\"n\">messages<\/span><span class=\"p\">.<\/span><span class=\"n\">SUCCESS<\/span>\n                <span class=\"p\">)<\/span>\n            <span class=\"k\">except<\/span> <span class=\"n\">ValidationError<\/span> <span class=\"k\">as<\/span> <span class=\"n\">e<\/span><span class=\"p\">:<\/span>\n                <span class=\"n\">self<\/span><span class=\"p\">.<\/span><span class=\"nf\">message_user<\/span><span class=\"p\">(<\/span>\n                    <span class=\"n\">request<\/span><span class=\"p\">,<\/span>\n                    <span class=\"sa\">f<\/span><span class=\"sh\">\"<\/span><span class=\"s\">Error al procesar entrada <\/span><span class=\"si\">{<\/span><span class=\"n\">entry<\/span><span class=\"si\">}<\/span><span class=\"s\">: <\/span><span class=\"si\">{<\/span><span class=\"nf\">str<\/span><span class=\"p\">(<\/span><span class=\"n\">e<\/span><span class=\"p\">)<\/span><span class=\"si\">}<\/span><span class=\"sh\">\"<\/span><span class=\"p\">,<\/span>\n                    <span class=\"n\">messages<\/span><span class=\"p\">.<\/span><span class=\"n\">ERROR<\/span>\n                <span class=\"p\">)<\/span>\n    <span class=\"n\">process_entries<\/span><span class=\"p\">.<\/span><span class=\"n\">short_description<\/span> <span class=\"o\">=<\/span> <span class=\"sh\">\"<\/span><span class=\"s\">Procesar entradas seleccionadas<\/span><span class=\"sh\">\"<\/span>\n\n<span class=\"c1\">## Tests Unitarios\n<\/span>\n<\/span><\/span><\/span><\/span><\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>\u067e\u0627\u06cc\u062a\u0648\u0646<br \/>\u0627\u0632 django.test import TestCase<br \/>\u0627\u0632 django.core.exceptions import ValidationError<br \/>\u0627\u0632 \u0648\u0627\u0631\u062f\u0627\u062a \u0627\u0639\u0634\u0627\u0631\u06cc \u0627\u0639\u0634\u0627\u0631\u06cc<br \/>\u0627\u0632 \u062a\u0627\u0631\u06cc\u062e \u0648\u0627\u0631\u062f\u0627\u062a \u062a\u0627\u0631\u06cc\u062e\u060c timedelta<br \/>\u0627\u0632 .models import AccountingPeriod, AccountingEntry<\/p>\n<p>\u06a9\u0644\u0627\u0633 AccountingPeriodTests (TestCase):<br \/>def setUp(self):<br \/>self.period = AccountingPeriod.objects.create(<br \/>name = &#8220;\u062f\u0648\u0631\u0647 \u062a\u0633\u062a&#8221;\u060c<br \/>start_date=date(2024, 1, 1),<br \/>end_date=date(2024, 1, 31),<br \/>status=&#8221;DRAFT&#8221;<br \/>)<\/p>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>def test_period_lifecycle(self):\n    \"\"\"Test del ciclo de vida completo de un per\u00edodo\"\"\"\n    # Verificar estado inicial\n    self.assertEqual(self.period.status, 'DRAFT')\n\n    # Activar per\u00edodo\n    self.period.activate()\n    self.assertEqual(self.period.status, 'ACTIVE')\n\n    # Crear entradas balanceadas\n    AccountingEntry.objects.create(\n        period=self.period,\n        date=date(2024, 1, 15),\n        description=\"Test Entry 1\",\n        debit_amount=Decimal('100.00'),\n        credit_amount=Decimal('0.00'),\n        status=\"PROCESSED\"\n    )\n    AccountingEntry.objects.create(\n        period=self.period,\n        date=date(2024, 1, 15),\n        description=\"Test Entry 2\",\n        debit_amount=Decimal('0.00'),\n        credit_amount=Decimal('100.00'),\n        status=\"PROCESSED\"\n    )\n\n    # Cerrar per\u00edodo\n    self.period.close()\n    self.assertEqual(self.period.status, 'CLOSED')\n    self.assertIsNotNone(self.period.closed_at)\n\ndef test_overlapping_periods(self):\n    \"\"\"Test de validaci\u00f3n de per\u00edodos superpuestos\"\"\"\n    with self.assertRaises(ValidationError):\n        AccountingPeriod.objects.create(\n            name=\"Overlapping Period\",\n            start_date=date(2024, 1, 15),\n            end_date=date(2024, 2, 15),\n            status=\"DRAFT\"\n        )\n\ndef test_invalid_status_transition(self):\n    \"\"\"Test de transiciones de estado inv\u00e1lidas\"\"\"\n    # No se puede pasar directamente de DRAFT a CLOSED\n    self.period.status=\"CLOSED\"\n    with self.assertRaises(ValidationError):\n        self.period.save()\n\n    # No se puede reabrir un per\u00edodo cerrado\n    self.period.activate()\n    self.period.close()\n    self.period.status=\"ACTIVE\"\n    with self.assertRaises(ValidationError):\n        self.period.save()\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>\u06a9\u0644\u0627\u0633 AccountingEntryTests (TestCase):<br \/>def setUp(self):<br \/>self.period = AccountingPeriod.objects.create(<br \/>name = &#8220;\u062f\u0648\u0631\u0647 \u062a\u0633\u062a&#8221;\u060c<br \/>start_date=date(2024, 1, 1),<br \/>end_date=date(2024, 1, 31),<br \/>\u0648\u0636\u0639\u06cc\u062a &#8220;\u0641\u0639\u0627\u0644&#8221;<br \/>)<\/p>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>def test_entry_validation(self):\n    \"\"\"Test de validaciones b\u00e1sicas de entradas\"\"\"\n    # Entrada con montos negativos\n    with self.assertRaises(ValidationError):\n        AccountingEntry.objects.create(\n            period=self.period,\n            date=date(2024, 1, 15),\n            description=\"Invalid Entry\",\n            debit_amount=Decimal('-100.00'),\n            credit_amount=Decimal('0.00')\n        )\n\n    # Entrada fuera del per\u00edodo\n    with self.assertRaises(ValidationError):\n        AccountingEntry.objects.create(\n            period=self.period,\n            date=date(2024, 2, 1),\n            description=\"Invalid Entry\",\n            debit_amount=Decimal('100.00'),\n            credit_amount=Decimal('0.00')\n        )\n\ndef test_entry_processing(self):\n    \"\"\"Test del procesamiento de entradas\"\"\"\n    entry = AccountingEntry.objects.create(\n        period=self.period,\n        date=date(2024, 1, 15),\n        description=\"Test Entry\",\n        debit_amount=Decimal('100.00'),\n        credit_amount=Decimal('0.00')\n    )\n\n    # Procesar entrada\n    entry.process()\n    self.assertEqual(entry.status, 'PROCESSED')\n    self.assertIsNotNone(entry.processed_at)\n\n    # No se puede procesar una entrada ya procesada\n    with self.assertRaises(ValidationError):\n        entry.process()\n\ndef test_closed_period_modifications(self):\n    \"\"\"Test de modificaciones en per\u00edodo cerrado\"\"\"\n    self.period.close()\n\n    # No se pueden crear nuevas entradas\n    with self.assertRaises(ValidationError):\n        AccountingEntry.objects.create(\n            period=self.period,\n            date=date(2024, 1, 15),\n            description=\"Test Entry\",\n            debit_amount=Decimal('100.00'),\n            credit_amount=Decimal('0.00')\n        )\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>\n## Ejemplo Pr\u00e1ctico: Sistema de Transferencias\n\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>\u067e\u0627\u06cc\u062a\u0648\u0646<\/p>\n<p>\u0627\u0632 \u0648\u0627\u0631\u062f\u0627\u062a \u0627\u0639\u0634\u0627\u0631\u06cc \u0627\u0639\u0634\u0627\u0631\u06cc<br \/>\u0627\u0632 \u062a\u0627\u0631\u06cc\u062e \u0648\u0627\u0631\u062f\u0627\u062a \u062a\u0627\u0631\u06cc\u062e<\/p>\n<p>def transfer_between_accounts(<br \/>period_id: int,<br \/>\u062a\u0627\u0631\u06cc\u062e: \u062a\u0627\u0631\u06cc\u062e\u060c<br \/>\u0645\u0642\u062f\u0627\u0631: \u0627\u0639\u0634\u0627\u0631\u06cc\u060c<br \/>\u062a\u0648\u0636\u06cc\u062d\u0627\u062a: \u062e<br \/>) -> \u062a\u0627\u067e\u0644:<br \/>&#8220;&#8221;&#8221;<br \/>\u0628\u0627 \u0627\u06cc\u062c\u0627\u062f \u062f\u0648 \u0648\u0631\u0648\u062f\u06cc \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc\u060c \u0628\u06cc\u0646 \u062d\u0633\u0627\u0628 \u0647\u0627 \u0627\u0646\u062a\u0642\u0627\u0644 \u0627\u06cc\u062c\u0627\u062f \u06a9\u0646\u06cc\u062f.<\/p>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>Returns:\n    tuple: (debit_entry, credit_entry)\n\"\"\"\ntry:\n    period = AccountingPeriod.objects.get(id=period_id)\n\n    # Crear entrada de d\u00e9bito\n    debit_entry = AccountingEntry.objects.create(\n        period=period,\n        date=date,\n        description=f\"D\u00c9BITO - {description}\",\n        debit_amount=amount,\n        credit_amount=Decimal('0.00')\n    )\n\n    # Crear entrada de cr\u00e9dito\n    credit_entry = AccountingEntry.objects.create(\n        period=period,\n        date=date,\n        description=f\"CR\u00c9DITO - {description}\",\n        debit_amount=Decimal('0.00'),\n        credit_amount=amount\n    )\n\n    # Procesar ambas entradas\n    debit_entry.process()\n    credit_entry.process()\n\n    return debit_entry, credit_entry\n\nexcept AccountingPeriod.DoesNotExist:\n    raise ValidationError(\"Per\u00edodo contable no encontrado\")\nexcept Exception as e:\n    raise ValidationError(f\"Error en la transferencia: {str(e)}\")\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>\u0633\u0639\u06cc \u06a9\u0646\u06cc\u062f:<br \/>period = AccountingPeriod.objects.get(<br \/>\u0634\u0631\u0648\u0639_\u062a\u0627\u0631\u06cc\u062e_<em>lte=date(2024, 1, 15),<br \/>\u067e\u0627\u06cc\u0627\u0646_\u062a\u0627\u0631\u06cc\u062e<\/em>_gte=date(2024\u060c 1\u060c 15)\u060c<br \/>\u0648\u0636\u0639\u06cc\u062a &#8220;\u0641\u0639\u0627\u0644&#8221;<br \/>)<\/p>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>debit, credit = transfer_between_accounts(\n    period_id=period.id,\n    date=date(2024, 1, 15),\n    amount=Decimal('1000.00'),\n    description=\"Transferencia entre cuentas\"\n)\n\nprint(f\"Transferencia exitosa: {debit.id}, {credit.id}\")\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>\u0628\u0647 \u062c\u0632 ValidationError \u0628\u0647 \u0635\u0648\u0631\u062a e:<br \/>print(f&#8221;\u062e\u0637\u0627: {str(e)}&#8221;)<\/p>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>\n## Queries \u00datiles\n\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>\u067e\u0627\u06cc\u062a\u0648\u0646<\/p>\n<p>def check_period_balance(period_id: int) -> dict:<br \/>period = AccountingPeriod.objects.get(id=period_id)<br \/>\u0645\u062c\u0645\u0648\u0639 = period.entries.aggregate(<br \/>total_debit=Sum(&#39;debit_amount&#39;),<br \/>total_credit=Sum(&#39;credit_amount&#39;)<br \/>)<\/p>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>return {\n    'period_name': period.name,\n    'total_debit': totals['total_debit'] or Decimal('0.00'),\n    'total_credit': totals['total_credit'] or Decimal('0.00'),\n    'is_balanced': period.is_balanced()\n}\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<p>def get_pending_entries(period_id: int) -> QuerySet:<br \/>\u0628\u0627\u0632\u06af\u0634\u062a AccountingEntry.objects.filter(<br \/>period_id=period_id\u060c<br \/>\u0648\u0636\u0639\u06cc\u062a &#8220;\u062f\u0631 \u0627\u0646\u062a\u0638\u0627\u0631&#8221;<br \/>)<\/p>\n<p>def get_period_summary(period_id: int) -> dict:<br \/>period = AccountingPeriod.objects.get(id=period_id)<br \/>entries_summary = period.entries.aggregate(<br \/>total_entries=Count(&#39;id&#39;),<br \/>pending_entries=Count(&#39;id&#39;, filter=Q(status=&#8221;PENDING&#8221;))<br \/>processed_entries=Count(&#39;id&#39;, filter=Q(status=&#8221;PROCESSED&#8221;))<br \/>rejected_entries=Count(&#39;id&#39;, filter=Q(status=&#8221;REJECTED&#8221;))<br \/>total_debit=Sum(&#39;debit_amount&#39;),<br \/>total_credit=Sum(&#39;credit_amount&#39;)<br \/>)<\/p>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>return {\n    'period_name': period.name,\n    'status': period.get_status_display(),\n    'duration': (period.end_date - period.start_date).days + 1,\n    'total_entries': entries_summary['total_entries'],\n    'pending_entries': entries_summary['pending_entries'],\n    'processed_entries': entries_summary['processed_entries'],\n    'rejected_entries': entries_summary['rejected_entries'],\n    'total_debit': entries_summary['total_debit'] or Decimal('0.00'),\n    'total_credit': entries_summary['total_credit'] or Decimal('0.00')\n}\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"highlight js-code-highlight\">\n<pre class=\"highlight plaintext\"><code>\n\n\n## Mejores Pr\u00e1cticas\n\n1. **Validaciones de Seguridad**\n   - Usar `PROTECT` en foreign keys para prevenir eliminaci\u00f3n en cascada\n   - Implementar validaciones a nivel de modelo\n   - Mantener registro de auditor\u00eda de cambios importantes\n\n2. **Manejo de Errores**\n   - Usar excepciones espec\u00edficas y mensajes claros\n   - Implementar rollback en operaciones complejas\n   - Registrar errores cr\u00edticos en logs\n\n3. **Patrones de Dise\u00f1o**\n   - State Pattern para estados de per\u00edodo y entradas\n   - Factory Method para crear entradas relacionadas\n   - Observer Pattern para auditor\u00eda y notificaciones\n\n## Conclusi\u00f3n\n\nEste sistema proporciona una base s\u00f3lida para manejar per\u00edodos contables en Django, con:\n- Validaciones robustas para mantener la integridad de los datos\n- Flujo claro de estados para per\u00edodos y entradas\n- Sistema extensible para diferentes tipos de operaciones contables\n<\/code><\/pre>\n<div class=\"highlight__panel js-actions-panel\">\n<div class=\"highlight__panel-action js-fullscreen-code-action\">\n    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-on\"><title>\u0648\u0627\u0631\u062f \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M16 3h6v6h-2V5h-4V3zM2 3h6v2H4v4H2V3zm18 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z\"\/>\n<\/svg><\/p>\n<p>    <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"20px\" height=\"20px\" viewbox=\"0 0 24 24\" class=\"highlight-action crayons-icon highlight-action--fullscreen-off\"><title>\u0627\u0632 \u062d\u0627\u0644\u062a \u062a\u0645\u0627\u0645 \u0635\u0641\u062d\u0647 \u062e\u0627\u0631\u062c \u0634\u0648\u06cc\u062f<\/title>\n    <path d=\"M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z\"\/>\n<\/svg><\/p>\n<\/div>\n<\/div>\n<\/div><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Summarize this content to 400 words in Persian Lang \u0645\u0642\u062f\u0645\u0647 \u0628\u0647 \u0639\u0646\u0648\u0627\u0646 \u06cc\u06a9 \u062a\u0648\u0633\u0639\u0647 \u062f\u0647\u0646\u062f\u0647 \u062c\u0646\u06af\u0648\u060c \u0627\u062d\u062a\u0645\u0627\u0644\u0627\u064b \u0628\u0627 \u0633\u06cc\u0633\u062a\u0645 \u0647\u0627\u06cc\u06cc \u06a9\u0627\u0631 \u06a9\u0631\u062f\u0647 \u0627\u06cc\u062f \u06a9\u0647 \u062f\u0627\u062f\u0647 \u0647\u0627\u06cc \u0632\u0645\u0627\u0646\u06cc \u0631\u0627 \u0645\u062f\u06cc\u0631\u06cc\u062a \u0645\u06cc \u06a9\u0646\u0646\u062f\u060c \u0627\u0645\u0627 \u062f\u0648\u0631\u0647 \u0647\u0627\u06cc \u062d\u0633\u0627\u0628\u062f\u0627\u0631\u06cc \u0645\u062a\u0641\u0627\u0648\u062a \u0627\u0633\u062a. \u0633\u06cc\u0633\u062a\u0645\u06cc \u0631\u0627 \u062a\u0635\u0648\u0631 \u06a9\u0646\u06cc\u062f \u06a9\u0647 \u062f\u0631 \u0622\u0646 \u0628\u0627\u06cc\u062f \u0627\u0637\u0645\u06cc\u0646\u0627\u0646 \u062d\u0627\u0635\u0644 \u06a9\u0646\u06cc\u062f \u06a9\u0647 \u062a\u0645\u0627\u0645 \u0639\u0645\u0644\u06cc\u0627\u062a \u0645\u0627\u0644\u06cc \u0628\u0647 \u062f\u0631\u0633\u062a\u06cc \u062f\u0631 &hellip;<\/p>\n","protected":false},"author":2,"featured_media":83069,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"fifu_image_url":"","fifu_image_alt":"","footnotes":""},"categories":[339],"tags":[],"class_list":["post-83068","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dev"],"_links":{"self":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/posts\/83068","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/comments?post=83068"}],"version-history":[{"count":0,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/posts\/83068\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/media\/83069"}],"wp:attachment":[{"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/media?parent=83068"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/categories?post=83068"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nabfollower.com\/blog\/wp-json\/wp\/v2\/tags?post=83068"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}