Mudanza de repo

This commit is contained in:
2025-09-22 18:43:29 -06:00
parent 26fe36ca52
commit d11d543bdc
193 changed files with 10998 additions and 0 deletions

View File

View File

@@ -0,0 +1,20 @@
from django.contrib import admin
from .models import Notificacion, TipoNotificacion
# Register your models here.
class NotificacionAdmin(admin.ModelAdmin):
list_display = ('tipo', 'dirigido', 'mensaje', 'fecha_envio', 'created_at', 'visto')
search_fields = ('mensaje', 'tipo__tipo', 'dirigido__username')
list_filter = ('tipo', 'visto', 'fecha_envio')
ordering = ('-created_at',)
date_hierarchy = 'fecha_envio'
class TipoNotificacionAdmin(admin.ModelAdmin):
list_display = ('tipo', 'descripcion')
search_fields = ('tipo',)
ordering = ('tipo',)
admin.site.register(Notificacion, NotificacionAdmin)
admin.site.register(TipoNotificacion, TipoNotificacionAdmin)
admin.site.empty_value_display = '-vacío-' # Display this when a field is empty

View File

@@ -0,0 +1,11 @@
from django.apps import AppConfig
class NotificacionesConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'api.notificaciones'
def ready(self):
import api.notificaciones.signals.notificaciones
# Import other signals if needed
# import api.notificaciones.signals.other_signal

View File

View File

@@ -0,0 +1,49 @@
# Generated by Django 5.2.3 on 2025-07-14 16:14
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='TipoNotificacion',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tipo', models.CharField(help_text='Tipo de notificación', max_length=100, unique=True)),
('descripcion', models.CharField(help_text='Descripción del tipo de notificación', max_length=200)),
],
options={
'verbose_name': 'Tipo de Notificación',
'verbose_name_plural': 'Tipos de Notificación',
'db_table': 'tipo_notificacion',
'ordering': ['tipo'],
},
),
migrations.CreateModel(
name='Notificacion',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('mensaje', models.TextField(help_text='Mensaje de la notificación')),
('fecha_envio', models.DateTimeField(blank=True, help_text='Fecha de envío de la notificación', null=True)),
('created_at', models.DateTimeField(auto_now_add=True, help_text='Fecha de creación de la notificación')),
('visto', models.BooleanField(default=False, help_text='Indica si la notificación ha sido vista')),
('dirigido', models.ForeignKey(help_text='Usuario al que se dirige la notificación', on_delete=django.db.models.deletion.CASCADE, related_name='notificaciones', to=settings.AUTH_USER_MODEL)),
('tipo', models.ForeignKey(help_text='Tipo de notificación', on_delete=django.db.models.deletion.CASCADE, related_name='notificaciones', to='notificaciones.tiponotificacion')),
],
options={
'verbose_name': 'Notificación',
'verbose_name_plural': 'Notificaciones',
'db_table': 'notificaciones',
'ordering': ['-created_at'],
},
),
]

View File

@@ -0,0 +1,35 @@
from django.db import models
from api.cuser.models import CustomUser
# Create your models here.
class TipoNotificacion(models.Model):
tipo = models.CharField(max_length=100, unique=True, help_text="Tipo de notificación")
descripcion = models.CharField(max_length=200, help_text="Descripción del tipo de notificación")
def __str__(self):
return self.tipo
class Meta:
verbose_name = "Tipo de Notificación"
verbose_name_plural = "Tipos de Notificación"
db_table = 'tipo_notificacion'
ordering = ['tipo']
class Notificacion(models.Model):
tipo = models.ForeignKey(TipoNotificacion, on_delete=models.CASCADE, related_name='notificaciones', help_text="Tipo de notificación")
dirigido = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='notificaciones', help_text="Usuario al que se dirige la notificación")
mensaje = models.TextField(help_text="Mensaje de la notificación")
fecha_envio = models.DateTimeField(blank=True, null=True, help_text="Fecha de envío de la notificación")
created_at = models.DateTimeField(auto_now_add=True, help_text="Fecha de creación de la notificación")
visto = models.BooleanField(default=False, help_text="Indica si la notificación ha sido vista")
def __str__(self):
return f"{self.tipo} - {self.created_at.strftime('%Y-%m-%d %H:%M:%S')}"
class Meta:
verbose_name = "Notificación"
verbose_name_plural = "Notificaciones"
db_table = 'notificaciones'
ordering = ['-created_at']

View File

View File

@@ -0,0 +1,25 @@
from rest_framework import serializers
from .models import Notificacion, TipoNotificacion
class TipoNotificacionSerializer(serializers.ModelSerializer):
class Meta:
model = TipoNotificacion
fields = ['id', 'tipo', 'descripcion']
read_only_fields = ['id', 'tipo', 'descripcion']
class NotificacionSerializer(serializers.ModelSerializer):
class Meta:
model = Notificacion
fields = [
'id',
'tipo',
'dirigido',
'mensaje',
'fecha_envio',
'created_at',
'visto'
]
read_only_fields = ['id', 'created_at', 'tipo', 'dirigido', 'fecha_envio', 'mensaje']

View File

View File

@@ -0,0 +1,34 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from api.notificaciones.models import Notificacion
from api.record.models import Document
@receiver(post_save, sender=Document)
def trigger_notificacion(sender, instance, created, **kwargs):
if created:
from api.cuser.models import CustomUser
from api.customs.models import Pedimento
from api.notificaciones.models import TipoNotificacion
# Obtener el tipo de notificación (puedes ajustar el nombre si tienes tipos definidos)
tipo_info, _ = TipoNotificacion.objects.get_or_create(tipo="info", defaults={"descripcion": "Notificación informativa"})
# Notificar a todos los usuarios de la organización
usuarios_org = CustomUser.objects.filter(organizacion=instance.organizacion)
for usuario in usuarios_org:
# Notificar solo a importadores cuyo RFC coincide
if (usuario.is_importador or usuario.groups.filter(name='Importador').exists()):
if usuario.rfc == instance.pedimento.contribuyente:
Notificacion.objects.create(
tipo=tipo_info,
dirigido=usuario,
mensaje=f"Se agregó el documento {instance.archivo} al pedimento {instance.pedimento.pedimento} \n {instance.document_type.nombre}",
)
# Notificar a otros roles (no importadores)
elif (usuario.is_superuser or usuario.groups.filter(name='Agente Aduanal').exists() or usuario.groups.filter(name='admin').exists()):
Notificacion.objects.create(
tipo=tipo_info,
dirigido=usuario,
mensaje=f"Se agregó el documento {instance.archivo} al pedimento {instance.pedimento.pedimento} \n {instance.document_type.nombre}",
)

View File

@@ -0,0 +1,77 @@
from django.urls import reverse
from rest_framework.test import APITestCase, APIClient
from rest_framework import status
from django.contrib.auth import get_user_model
from .models import Notificacion, TipoNotificacion
from api.organization.models import Organizacion
User = get_user_model()
class NotificacionesViewSetTests(APITestCase):
def setUp(self):
self.org = Organizacion.objects.create(nombre="OrgTest", is_active=True, is_verified=True)
self.org2 = Organizacion.objects.create(nombre="OrgTest2", is_active=True, is_verified=True)
self.admin = User.objects.create_user(username="admin", password="adminpass", organizacion=self.org)
self.admin.groups.create(name="admin")
self.superuser = User.objects.create_superuser(username="superuser", password="superpass")
self.importador = User.objects.create_user(username="importador", password="importpass", organizacion=self.org2, is_importador=True, rfc="RFC123456789")
self.importador.groups.create(name="importador")
self.client = APIClient()
def test_list_tipo_notificacion(self):
TipoNotificacion.objects.create(tipo="info", descripcion="informativa")
self.client.force_authenticate(user=self.admin)
url = reverse('tipo-notificacion-list')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(len(response.data) >= 1)
def test_admin_sees_only_org_notificaciones(self):
tipo = TipoNotificacion.objects.create(tipo="info", descripcion="informativa")
notif1 = Notificacion.objects.create(tipo=tipo, dirigido=self.admin, mensaje="msg1")
notif2 = Notificacion.objects.create(tipo=tipo, dirigido=self.importador, mensaje="msg2")
self.client.force_authenticate(user=self.admin)
url = reverse('notificacion-list')
response = self.client.get(url)
mensajes = [n['mensaje'] for n in response.data]
self.assertIn("msg1", mensajes)
self.assertNotIn("msg2", mensajes)
def test_superuser_sees_all_notificaciones(self):
tipo = TipoNotificacion.objects.create(tipo="info", descripcion="informativa")
notif1 = Notificacion.objects.create(tipo=tipo, dirigido=self.admin, mensaje="msg1")
notif2 = Notificacion.objects.create(tipo=tipo, dirigido=self.importador, mensaje="msg2")
self.client.force_authenticate(user=self.superuser)
url = reverse('notificacion-list')
response = self.client.get(url)
mensajes = [n['mensaje'] for n in response.data]
self.assertIn("msg1", mensajes)
self.assertIn("msg2", mensajes)
def test_importador_sees_only_own_notificaciones(self):
tipo = TipoNotificacion.objects.create(tipo="info", descripcion="informativa")
notif1 = Notificacion.objects.create(tipo=tipo, dirigido=self.admin, mensaje="msg1")
notif2 = Notificacion.objects.create(tipo=tipo, dirigido=self.importador, mensaje="msg2")
self.client.force_authenticate(user=self.importador)
url = reverse('notificacion-list')
response = self.client.get(url)
mensajes = [n['mensaje'] for n in response.data]
self.assertNotIn("msg1", mensajes)
self.assertIn("msg2", mensajes)
def test_superuser_can_create_notificacion(self):
tipo = TipoNotificacion.objects.create(tipo="info", descripcion="informativa")
self.client.force_authenticate(user=self.superuser)
url = reverse('notificacion-list')
data = {"tipo": tipo.id, "dirigido": self.admin.id, "mensaje": "msg3"}
response = self.client.post(url, data)
self.assertIn(response.status_code, [status.HTTP_201_CREATED, status.HTTP_200_OK])
def test_admin_cannot_create_notificacion(self):
tipo = TipoNotificacion.objects.create(tipo="info", descripcion="informativa")
self.client.force_authenticate(user=self.admin)
url = reverse('notificacion-list')
data = {"tipo": tipo.id, "dirigido": self.importador.id, "mensaje": "msg4"}
response = self.client.post(url, data)
self.assertNotIn(response.status_code, [status.HTTP_201_CREATED, status.HTTP_200_OK])

View File

@@ -0,0 +1,13 @@
from rest_framework import routers
from django.urls import path, include
from .views import TipoNotificacionViewSet, NotificacionViewSet
# Create a router and register the viewsets
router = routers.DefaultRouter()
router.register(r'tipos', TipoNotificacionViewSet, basename='tipo-notificacion')
router.register(r'notificaciones', NotificacionViewSet, basename='notificacion')
# Create a router and register the healthcheck view
urlpatterns = [
path('', include(router.urls)),
]

View File

@@ -0,0 +1,50 @@
from django.shortcuts import render
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from .models import Notificacion, TipoNotificacion
from .serializers import NotificacionSerializer, TipoNotificacionSerializer
from core.permissions import (
IsSameOrganization,
IsSameOrganizationDeveloper,
IsSameOrganizationAndAdmin,
IsSuperUser
)
# Create your views here.
class TipoNotificacionViewSet(viewsets.ModelViewSet):
queryset = TipoNotificacion.objects.all()
serializer_class = TipoNotificacionSerializer
http_method_names = ['get']
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
my_tags = ['Notificaciones']
def get_queryset(self):
return self.queryset.order_by('tipo')
class NotificacionViewSet(viewsets.ModelViewSet):
queryset = Notificacion.objects.all()
serializer_class = NotificacionSerializer
http_method_names = ['get', 'post', 'put', 'patch', 'delete']
filterset_fields = ['visto']
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
my_tags = ['Notificaciones']
def get_queryset(self):
# Evita error en generación de esquema Swagger
if getattr(self, 'swagger_fake_view', False):
return Notificacion.objects.none()
user = self.request.user
if not user.is_authenticated:
return Notificacion.objects.none()
return Notificacion.objects.filter(dirigido=user)
def perform_create(self, serializer):
if not self.request.user.is_authenticated:
raise PermissionDenied("Usuario no autenticado")
if self.request.user.is_superuser:
# Allow superusers and admins to create notifications for any user
serializer.save()
raise PermissionDenied("No tienes permiso para crear notificaciones para otros usuarios")