diff --git a/.vscode/fastapi-crud.code-snippets b/.vscode/fastapi-crud.code-snippets index 72ed0b3..a2adc23 100644 --- a/.vscode/fastapi-crud.code-snippets +++ b/.vscode/fastapi-crud.code-snippets @@ -6,6 +6,7 @@ "\"\"\"", "${1:Modulos} v4, CRUD (create, read, update, and delete)", "\"\"\"", + "", "from typing import Any", "", "from sqlalchemy.orm import Session", @@ -45,6 +46,7 @@ "\"\"\"", "${1:Modulos} v4, CRUD (create, read, update, and delete)", "\"\"\"", + "", "from typing import Any", "", "from sqlalchemy.orm import Session", diff --git a/.vscode/fastapi-models.code-snippets b/.vscode/fastapi-models.code-snippets index b4cc3ec..7ae0b99 100644 --- a/.vscode/fastapi-models.code-snippets +++ b/.vscode/fastapi-models.code-snippets @@ -6,6 +6,7 @@ "\"\"\"", "${1:Modulos}, modelos", "\"\"\"", + "", "from collections import OrderedDict", "", "from sqlalchemy import Boolean, Column, Date, Enum, ForeignKey, Integer, String", diff --git a/.vscode/fastapi-paths.code-snippets b/.vscode/fastapi-paths.code-snippets index 0031c02..ef9e36e 100644 --- a/.vscode/fastapi-paths.code-snippets +++ b/.vscode/fastapi-paths.code-snippets @@ -6,6 +6,7 @@ "\"\"\"", "${1:Modulos} v4, rutas (paths)", "\"\"\"", + "", "from typing import Annotated", "", "from fastapi import APIRouter, Depends, HTTPException, status", @@ -63,6 +64,7 @@ "\"\"\"", "${1:Modulos} v4, rutas (paths)", "\"\"\"", + "", "from typing import Annotated", "", "from fastapi import APIRouter, Depends, HTTPException, status", diff --git a/.vscode/fastapi-schemas.code-snippets b/.vscode/fastapi-schemas.code-snippets index 283d05c..686325b 100644 --- a/.vscode/fastapi-schemas.code-snippets +++ b/.vscode/fastapi-schemas.code-snippets @@ -6,7 +6,6 @@ "\"\"\"", "${1:Modulos} v4, esquemas de pydantic", "\"\"\"", - "from datetime import date", "", "from pydantic import BaseModel, ConfigDict", "", @@ -19,16 +18,15 @@ "\tid: int | None = None", "\trelacion_id: int | None = None", "\trelacion_nombre: str | None = None", - "\tfecha: date | None = None", + "\tclave: str | None = None", "\tnombre: str | None = None", - "\tdescripcion: str | None = None", - "\tarchivo: str | None = None", - "\turl: str | None = None", "\tmodel_config = ConfigDict(from_attributes=True)", "", "", - "class One${2:EsquemaOut}(${2:EsquemaOut}, OneBaseOut):", + "class One${2:EsquemaOut}(OneBaseOut):", "\t\"\"\" Esquema para entregar un ${4:singular} \"\"\"", + "", + "\tdata: ${2:EsquemaOut} | None = None", "" ], "description": "Contenido para schemas.py" diff --git a/carina/app.py b/carina/app.py index ce625c2..3b97e56 100644 --- a/carina/app.py +++ b/carina/app.py @@ -13,6 +13,7 @@ from .v4.distritos.paths import distritos from .v4.entradas_salidas.paths import entradas_salidas from .v4.estados.paths import estados +from .v4.exh_areas.paths import exh_areas from .v4.exh_exhortos.paths import exh_exhortos from .v4.exh_exhortos_archivos.paths import exh_exhortos_archivos from .v4.exh_exhortos_partes.paths import exh_exhortos_partes @@ -53,6 +54,7 @@ def create_app() -> FastAPI: app.include_router(distritos) app.include_router(entradas_salidas, include_in_schema=False) app.include_router(estados) + app.include_router(exh_areas) app.include_router(exh_exhortos) app.include_router(exh_exhortos_archivos) app.include_router(exh_exhortos_partes) diff --git a/carina/core/estados/models.py b/carina/core/estados/models.py index 98d8d84..6d84e2a 100644 --- a/carina/core/estados/models.py +++ b/carina/core/estados/models.py @@ -27,4 +27,4 @@ class Estado(Base, UniversalMixin): def __repr__(self): """Representación""" - return f"" + return f"" diff --git a/carina/core/exh_areas/__init__.py b/carina/core/exh_areas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/carina/core/exh_areas/models.py b/carina/core/exh_areas/models.py new file mode 100644 index 0000000..dfc24d6 --- /dev/null +++ b/carina/core/exh_areas/models.py @@ -0,0 +1,30 @@ +""" +Exh Areas, modelos +""" + +from sqlalchemy import Column, Integer, String +from sqlalchemy.orm import relationship + +from lib.database import Base +from lib.universal_mixin import UniversalMixin + + +class ExhArea(Base, UniversalMixin): + """ExhArea""" + + # Nombre de la tabla + __tablename__ = "exh_areas" + + # Clave primaria + id = Column(Integer, primary_key=True) + + # Columnas + clave = Column(String(16), unique=True, nullable=False) + descripcion = Column(String(256), nullable=False) + + # Hijos + exh_exhortos = relationship("ExhExhorto", back_populates="exh_area") + + def __repr__(self): + """Representación""" + return f"" diff --git a/carina/core/exh_exhortos/models.py b/carina/core/exh_exhortos/models.py index eff3e2a..ce40624 100644 --- a/carina/core/exh_exhortos/models.py +++ b/carina/core/exh_exhortos/models.py @@ -2,7 +2,7 @@ Exh Exhortos, modelos """ -from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, func +from sqlalchemy import Column, DateTime, Enum, ForeignKey, Integer, String from sqlalchemy.orm import relationship from lib.database import Base @@ -12,6 +12,16 @@ class ExhExhorto(Base, UniversalMixin): """ExhExhorto""" + ESTADOS = { + "PENDIENTE": "Pendiente", + "RECIBIDO": "Recibido", + } + + REMITENTES = { + "INTERNO": "Interno", + "EXTERNO": "Externo", + } + # Nombre de la tabla __tablename__ = "exh_exhortos" @@ -64,7 +74,7 @@ class ExhExhorto(Base, UniversalMixin): tipo_diligenciacion_nombre = Column(String(256)) # Fecha y hora en que el Poder Judicial exhortante registró que se envió el exhorto en su hora local. En caso de no enviar este dato, el Poder Judicial exhortado puede tomar su fecha hora local. - fecha_origen = Column(DateTime, server_default=func.now()) + fecha_origen = Column(DateTime, nullable=False) # Texto simple que contenga información extra o relevante sobre el exhorto. observaciones = Column(String(1024)) @@ -75,80 +85,19 @@ class ExhExhorto(Base, UniversalMixin): # ArchivoARecibir[] SI Colección de los datos referentes a los archivos que se van a recibir el Poder Judicial exhortado en el envío del Exhorto. exh_exhortos_archivos = relationship("ExhExhortoArchivo", back_populates="exh_exhorto") - # Propiedades que se van a cargar despues - partes = [] - archivos = [] - - @property - def exhortoOrigenId(self): - """ID del exhorto de origen""" - return self.exhorto_origen_id - - @property - def municipioDestinoId(self): - """Clave INEGI del municipio de destino""" - return self.municipio_destino_id - - @property - def materiaClave(self): - """Clave de la materia""" - return self.materia.clave - - @property - def estadoOrigenId(self): - """Clave INEGI del estado de origen""" - return self.municipio_origen.estado.clave - - @property - def municipioOrigenId(self): - """Clave INEGI del municipio de origen""" - return self.municipio_origen.clave - - @property - def juzgadoOrigenId(self): - """ID del juzgado de origen""" - return self.juzgado_origen_id - - @property - def juzgadoOrigenNombre(self): - """Nombre del juzgado de origen""" - return self.juzgado_origen_nombre - - @property - def numeroExpedienteOrigen(self): - """Número de expediente de origen""" - return self.numero_expediente_origen - - @property - def numeroOficioOrigen(self): - """Número de oficio de origen""" - return self.numero_oficio_origen - - @property - def tipoJuicioAsuntoDelitos(self): - """Tipo de juicio""" - return self.tipo_juicio_asunto_delitos - - @property - def juezExhortante(self): - """Juez exhortante""" - return self.juez_exhortante - - @property - def diasResponder(self): - """Días para responder""" - return self.dias_responder - - @property - def tipoDiligenciacionNombre(self): - """Tipo de diligenciación""" - return self.tipo_diligenciacion_nombre - - @property - def fechaOrigen(self): - """Fecha de origen""" - return self.fecha_origen + # GUID/UUID... que sea único + folio_seguimiento = Column(String(64), nullable=False, unique=True) + + # Área de recepción + exh_area_id = Column(Integer, ForeignKey("exh_areas.id"), index=True, nullable=False) + exh_area = relationship("ExhArea", back_populates="exh_exhortos") + + # Estado de recepción del documento + estado = Column(Enum(*ESTADOS, name="exh_exhortos_estados", native_enum=False), nullable=True) + + # Campo para saber si es un proceso interno o extorno + remitente = Column(Enum(*REMITENTES, name="exh_exhortos_remitentes", native_enum=False), nullable=True) def __repr__(self): """Representación""" - return f"" + return f"" diff --git a/carina/core/exh_exhortos_archivos/models.py b/carina/core/exh_exhortos_archivos/models.py index 6254c13..1256026 100644 --- a/carina/core/exh_exhortos_archivos/models.py +++ b/carina/core/exh_exhortos_archivos/models.py @@ -2,7 +2,7 @@ Exh Exhortos Archivos, modelos """ -from sqlalchemy import Column, Enum, ForeignKey, Integer, String +from sqlalchemy import Column, DateTime, Enum, ForeignKey, Integer, String from sqlalchemy.orm import relationship from lib.database import Base @@ -33,7 +33,7 @@ class ExhExhortoArchivo(Base, UniversalMixin): # Hash SHA1 en hexadecimal que corresponde al archivo a recibir. Esto para comprobar la integridad del archivo. hash_sha1 = Column(String(256)) - # Hash SHA256 en hexadecimal que corresponde al archivo a recibir. Esto apra comprobar la integridad del archivo. + # Hash SHA256 en hexadecimal que corresponde al archivo a recibir. Esto para comprobar la integridad del archivo. hash_sha256 = Column(String(256)) # Identificador del tipo de documento que representa el archivo: @@ -45,28 +45,14 @@ class ExhExhortoArchivo(Base, UniversalMixin): # URL del archivo en Google Storage url = Column(String(512), nullable=False, default="", server_default="") - # Estado de la entrega del archivo - estado = Column(Enum(*ESTADOS, name="exh_exhortos_archivos_estados", native_enum=False), nullable=False) + # Estado de recepción del documento + estado = Column(Enum(*ESTADOS, name="exh_exhortos_archivos_estados", native_enum=False), nullable=True) - @property - def nombreArchivo(self): - """Nombre del archivo""" - return self.nombre_archivo + # Tamaño del archivo recibido en bytes + tamano = Column(Integer, nullable=False) - @property - def hashSha1(self): - """Hash SHA1""" - return self.hash_sha1 - - @property - def hashSha256(self): - """Hash SHA256""" - return self.hash_sha256 - - @property - def tipoDocumento(self): - """Tipo de documento""" - return self.tipo_documento + # Fecha y hora de recepción del documento + fecha_hora_recepcion = Column(DateTime, nullable=False) def __repr__(self): """Representación""" diff --git a/carina/core/exh_exhortos_partes/models.py b/carina/core/exh_exhortos_partes/models.py index e075a2a..384d03c 100644 --- a/carina/core/exh_exhortos_partes/models.py +++ b/carina/core/exh_exhortos_partes/models.py @@ -54,31 +54,10 @@ class ExhExhortoParte(Base, UniversalMixin): tipo_parte_nombre = Column(String(256)) @property - def apellidoPaterno(self): - """Apellido paterno""" - return self.apellido_paterno + def nombre_completo(self): + """Junta nombres, apellido_paterno y apellido materno""" + return self.nombre + " " + self.apellido_paterno + " " + self.apellido_materno - @property - def apellidoMaterno(self): - """Apellido materno""" - return self.apellido_materno - - @property - def esPersonaMoral(self): - """Es persona moral""" - return self.es_persona_moral - - @property - def tipoParte(self): - """Tipo de parte""" - return self.tipo_parte - - @property - def tipoParteNombre(self): - """Nombre del tipo de parte""" - return self.tipo_parte_nombre - - @property def __repr__(self): """Representación""" return f"" diff --git a/carina/core/municipios/models.py b/carina/core/municipios/models.py index eccdbe9..f2d36d7 100644 --- a/carina/core/municipios/models.py +++ b/carina/core/municipios/models.py @@ -31,4 +31,4 @@ class Municipio(Base, UniversalMixin): def __repr__(self): """Representación""" - return f"" + return f"" diff --git a/carina/v4/autoridades/paths.py b/carina/v4/autoridades/paths.py index 8c8ea75..c2f539e 100644 --- a/carina/v4/autoridades/paths.py +++ b/carina/v4/autoridades/paths.py @@ -19,7 +19,7 @@ autoridades = APIRouter(prefix="/v4/autoridades", tags=["autoridades"]) -@autoridades.get("/", response_model=CustomList[AutoridadOut]) +@autoridades.get("", response_model=CustomList[AutoridadOut]) async def paginado_autoridades( current_user: Annotated[UsuarioInDB, Depends(get_current_active_user)], database: Annotated[Session, Depends(get_db)], diff --git a/carina/v4/distritos/paths.py b/carina/v4/distritos/paths.py index b3e2b93..e8fc751 100644 --- a/carina/v4/distritos/paths.py +++ b/carina/v4/distritos/paths.py @@ -19,7 +19,7 @@ distritos = APIRouter(prefix="/v4/distritos", tags=["distritos"]) -@distritos.get("/", response_model=CustomList[DistritoOut]) +@distritos.get("", response_model=CustomList[DistritoOut]) async def paginado_distritos( current_user: Annotated[UsuarioInDB, Depends(get_current_active_user)], database: Annotated[Session, Depends(get_db)], diff --git a/carina/v4/exh_areas/__init__.py b/carina/v4/exh_areas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/carina/v4/exh_areas/crud.py b/carina/v4/exh_areas/crud.py new file mode 100644 index 0000000..5ef345e --- /dev/null +++ b/carina/v4/exh_areas/crud.py @@ -0,0 +1,41 @@ +""" +Exh Areas v4, CRUD (create, read, update, and delete) +""" + +from typing import Any + +from sqlalchemy.orm import Session + +from lib.exceptions import MyIsDeletedError, MyNotExistsError, MyNotValidParamError +from lib.safe_string import safe_clave + +from ...core.exh_areas.models import ExhArea + + +def get_exh_areas(database: Session) -> Any: + """Consultar los areas activos""" + return database.query(ExhArea).filter_by(estatus="A").order_by(ExhArea.clave) + + +def get_exh_area(database: Session, exh_area_id: int) -> ExhArea: + """Consultar un area por su id""" + exh_area = database.query(ExhArea).get(exh_area_id) + if exh_area is None: + raise MyNotExistsError("No existe ese area") + if exh_area.estatus != "A": + raise MyIsDeletedError("No es activo ese area, está eliminado") + return exh_area + + +def get_exh_area_with_clave(database: Session, exh_area_clave: str) -> ExhArea: + """Consultar un area por su clave""" + try: + clave = safe_clave(exh_area_clave) + except ValueError as error: + raise MyNotValidParamError(str(error)) from error + exh_area = database.query(ExhArea).filter_by(clave=clave).first() + if exh_area is None: + raise MyNotExistsError("No existe ese area") + if exh_area.estatus != "A": + raise MyIsDeletedError("No es activo ese area, está eliminado") + return exh_area diff --git a/carina/v4/exh_areas/paths.py b/carina/v4/exh_areas/paths.py new file mode 100644 index 0000000..84d80c5 --- /dev/null +++ b/carina/v4/exh_areas/paths.py @@ -0,0 +1,50 @@ +""" +Exh Areas v4, rutas (paths) +""" + +from typing import Annotated + +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi_pagination.ext.sqlalchemy import paginate + +from lib.database import Session, get_db +from lib.exceptions import MyAnyError +from lib.fastapi_pagination_custom_page import CustomPage + +from ...core.permisos.models import Permiso +from ..usuarios.authentications import UsuarioInDB, get_current_active_user +from .crud import get_exh_areas, get_exh_area_with_clave +from .schemas import ExhAreaOut, OneExhAreaOut + +exh_areas = APIRouter(prefix="/v4/exh_areas", tags=["categoria"]) + + +@exh_areas.get("", response_model=CustomPage[ExhAreaOut]) +async def paginado_exh_areas( + current_user: Annotated[UsuarioInDB, Depends(get_current_active_user)], + database: Annotated[Session, Depends(get_db)], +): + """Paginado de areas""" + if current_user.permissions.get("EXH AREAS", 0) < Permiso.VER: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden") + try: + resultados = get_exh_areas(database) + except MyAnyError as error: + return CustomPage(success=False, errors=[str(error)]) + return paginate(resultados) + + +@exh_areas.get("/{materia_clave}", response_model=OneExhAreaOut) +async def detalle_exh_area( + current_user: Annotated[UsuarioInDB, Depends(get_current_active_user)], + database: Annotated[Session, Depends(get_db)], + materia_clave: str, +): + """Detalle de una singular a partir de su clave""" + if current_user.permissions.get("MODULO", 0) < Permiso.VER: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden") + try: + exh_area = get_exh_area_with_clave(database, materia_clave) + except MyAnyError as error: + return OneExhAreaOut(success=False, errors=[str(error)]) + return OneExhAreaOut.model_validate(exh_area) diff --git a/carina/v4/exh_areas/schemas.py b/carina/v4/exh_areas/schemas.py new file mode 100644 index 0000000..9d9ac97 --- /dev/null +++ b/carina/v4/exh_areas/schemas.py @@ -0,0 +1,22 @@ +""" +Exh Areas v4, esquemas de pydantic +""" + +from pydantic import BaseModel, ConfigDict + +from lib.schemas_base import OneBaseOut + + +class ExhAreaOut(BaseModel): + """Esquema para entregar areas""" + + id: int | None = None + clave: str | None = None + nombre: str | None = None + model_config = ConfigDict(from_attributes=True) + + +class OneExhAreaOut(OneBaseOut): + """Esquema para entregar un area""" + + data: ExhAreaOut | None = None diff --git a/carina/v4/exh_exhortos/crud.py b/carina/v4/exh_exhortos/crud.py index 68b1d40..9f12737 100644 --- a/carina/v4/exh_exhortos/crud.py +++ b/carina/v4/exh_exhortos/crud.py @@ -2,7 +2,9 @@ Exh Exhortos v4, CRUD (create, read, update, and delete) """ +from datetime import datetime from typing import Any +import uuid from sqlalchemy.orm import Session @@ -103,6 +105,13 @@ def create_exh_exhorto(database: Session, exh_exhorto_in: ExhExhortoIn) -> ExhEx # Texto simple que contenga información extra o relevante sobre el exhorto. exh_exhorto.observaciones = exh_exhorto_in.observaciones + # GUID/UUID... que sea único + random_uuid = uuid.uuid4() + exh_exhorto.folio_seguimiento = str(random_uuid) + + # Área de recepción, 1 = NO DEFINIDO + exh_exhorto.exh_area_id = 1 + # Iniciar la transaccion, agregar el exhorto database.add(exh_exhorto) @@ -131,6 +140,8 @@ def create_exh_exhorto(database: Session, exh_exhorto_in: ExhExhortoIn) -> ExhEx hash_sha256=archivo.hashSha256, tipo_documento=archivo.tipoDocumento, estado="PENDIENTE", + tamano=0, + fecha_hora_recepcion=datetime.now(), ) ) diff --git a/carina/v4/materias/paths.py b/carina/v4/materias/paths.py index edc2559..89bfbbb 100644 --- a/carina/v4/materias/paths.py +++ b/carina/v4/materias/paths.py @@ -1,6 +1,7 @@ """ Materias v4, rutas (paths) """ + from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, status @@ -18,7 +19,7 @@ materias = APIRouter(prefix="/v4/materias", tags=["materias"]) -@materias.get("/", response_model=CustomList[MateriaOut]) +@materias.get("", response_model=CustomList[MateriaOut]) async def paginado_materias( current_user: Annotated[UsuarioInDB, Depends(get_current_active_user)], database: Annotated[Session, Depends(get_db)], @@ -46,7 +47,4 @@ async def detalle_materia( materia = get_materia_with_clave(database, materia_clave) except MyAnyError as error: return OneMateriaOut(success=False, errors=[str(error)]) - return OneMateriaOut( - success=True, - data=materia - ) + return OneMateriaOut(success=True, data=materia)