first commit

This commit is contained in:
2026-04-01 13:48:40 -07:00
commit 61d386a7c7
178 changed files with 3827 additions and 0 deletions

0
app/core/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

184
app/core/affidavit.py Normal file
View File

@@ -0,0 +1,184 @@
# core/affidavit.py
from datetime import datetime
from typing import Any, Dict, Optional, List
import hashlib
import json
from sqlalchemy.orm import Session
from app.modules.affidavit_logs.models import Operation, StatusOP, AffidavitRecord
class LegalAffidavit:
"""
Certificador Legal Digital
Cada operación genera un acta notarial inmutable
"""
def __init__(self, db: Session, user, operation: str, client_id: int = None):
self.db = db
self.user = user
self.operation = operation
self.client_id = client_id
self.timestamp = datetime.utcnow()
self.entities_affected: List[Dict] = []
self.rules_validated: List[Dict] = []
self.evidence: List[Dict] = []
self._record = None
def _get_operation_enum(self) -> Operation:
"""Convierte string de operación a Enum Operation"""
operation_map = {
"create": Operation.CREATED,
"CREATE": Operation.CREATED,
"update": Operation.UPDATED,
"UPDATE": Operation.UPDATED,
"delete": Operation.DELETED,
"DELETE": Operation.DELETED,
"cancel": Operation.CANCELED,
"CANCEL": Operation.CANCELED,
"tried": Operation.TRIED,
"TRIED": Operation.TRIED,
}
return operation_map.get(self.operation.lower(), Operation.TRIED)
def _get_status_enum(self, success: bool) -> StatusOP:
"""Convierte resultado a StatusOP"""
if success:
return StatusOP.COMPLETED
return StatusOP.INVALID
def _hash_data(self, data: Any) -> str:
"""Genera hash para integridad de datos"""
data_str = json.dumps(data, sort_keys=True, default=str)
return hashlib.sha256(data_str.encode()).hexdigest()
def certify_entity(self, entity_type: str, entity_id: int, entity_data: Dict) -> 'LegalAffidavit':
"""Certifica que una entidad fue afectada"""
self.entities_affected.append({
"type": entity_type,
"id": entity_id,
"data_hash": self._hash_data(entity_data),
"timestamp": self.timestamp.isoformat()
})
return self
def validate_rule(self, rule_name: str, rule_result: bool, rule_details: str = "") -> 'LegalAffidavit':
"""Valida una regla de negocio"""
self.rules_validated.append({
"rule": rule_name,
"passed": rule_result,
"details": rule_details,
"validated_at": self.timestamp.isoformat()
})
if not rule_result:
raise AffidavitValidationError(f"Regla '{rule_name}' no cumplida: {rule_details}")
return self
def add_evidence(self, evidence_type: str, evidence_data: Any) -> 'LegalAffidavit':
"""Agrega evidencia digital"""
self.evidence.append({
"type": evidence_type,
"data": evidence_data,
"hash": self._hash_data(evidence_data),
"timestamp": self.timestamp.isoformat()
})
return self
def _generate_signature(self) -> str:
"""Genera firma digital del acta"""
data_to_sign = f"{self.user.id}{self.timestamp.isoformat()}{self.operation}{json.dumps(self.entities_affected, sort_keys=True)}"
return hashlib.sha512(data_to_sign.encode()).hexdigest()
def _build_description(self) -> str:
"""Construye la descripción del acta"""
entities = ", ".join([f"{e['type']}:{e['id']}" for e in self.entities_affected])
rules = ", ".join([r['rule'] for r in self.rules_validated if r['passed']])
desc = f"Operación {self.operation} por usuario {self.user.email}"
if entities:
desc += f" | Entidades: {entities}"
if rules:
desc += f" | Reglas: {rules}"
return desc
def sign(self, success: bool = True) -> Dict:
"""
Firma y guarda el acta notarial en la base de datos
"""
description = self._build_description()
signature = self._generate_signature()
status = self._get_status_enum(success)
operation_enum = self._get_operation_enum()
# Crear registro en BD
self._record = AffidavitRecord(
operation=operation_enum,
user_id=self.user.id,
client_id=self.client_id,
description=description,
adjustment=json.dumps(self.evidence) if self.evidence else None,
status=status,
sign=signature,
created_at=self.timestamp,
created_by=self.user.id
)
self.db.add(self._record)
self.db.commit()
self.db.refresh(self._record)
return {
"affidavit_id": self._record.id,
"operation": self.operation,
"user": {
"id": self.user.id,
"email": self.user.email,
"user_type": self.user.user_type,
"rol_operativo": self.user.rol_operativo
},
"timestamp": self.timestamp.isoformat(),
"description": description,
"status": status.value,
"signature": signature,
"entities_affected": self.entities_affected,
"rules_validated": self.rules_validated
}
def get_record_id(self) -> Optional[int]:
"""Retorna el ID del registro creado"""
return self._record.id if self._record else None
class AffidavitValidationError(Exception):
"""Error de validación notarial"""
pass
def certify_operation(operation_name: str):
"""Decorador para certificar operaciones automáticamente"""
def decorator(func):
async def wrapper(*args, **kwargs):
db = kwargs.get('db')
user = kwargs.get('current_user')
client_id = kwargs.get('client_id')
if not db and args and hasattr(args[0], 'db'):
db = args[0].db
if db and user:
affidavit = LegalAffidavit(db, user, operation_name, client_id)
kwargs['affidavit'] = affidavit
try:
result = await func(*args, **kwargs)
affidavit.sign(success=True)
return result
except Exception as e:
affidavit.sign(success=False)
raise
return await func(*args, **kwargs)
return wrapper
return decorator

160
app/core/auth.py Normal file
View File

@@ -0,0 +1,160 @@
from datetime import datetime, timedelta
from typing import Optional, List
import os
#========================
import jwt
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
security = HTTPBearer()
SECRET_KEY = os.getenv("SECRET_KEY", "xma-secret-key-change-in-production")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_HOURS = int(os.getenv("ACCESS_TOKEN_EXPIRE_HOURS", "1"))
class UserType:
""" User type with permisions"""
ROOT ="root"
ADMIN ="admin"
PRIVILEGED ="privileged"
ADMIN_LICENCIAS ="admin_licencias"
UPDATER = "updater"
USER ="user"
HIERARCHY = {
ROOT :6,
ADMIN :5,
ADMIN_LICENCIAS : 4,
PRIVILEGED : 3,
UPDATER : 2,
USER : 1,
}
@classmethod
def has_permission(cls, user_type: str, required_type: str) -> bool:
""" VERIFY if user_type has permition equal o plus required_type"""
return cls.HIERARCHY.get(user_type, 0) >= cls.HIERARCHY.get(required_type, 0)
class OperationalRole:
""" Role's"""
COMPRAS ="compras"
VENTAS ="ventas"
LOGISTICA ="logistica"
ADUANA_SOFT ="aduana_soft"
CLIENTE ="cliente"
OPERATIVO ="operativo"
class AuthService:
""" Service auth"""
def __init__(self):
self.secret_key = SECRET_KEY
self.algorithm = ALGORITHM
def create_token(self, user_id: int, email: str, user_type: str, rol_operativo: str) -> str:
expire = datetime.utcnow() + timedelta(hours=ACCESS_TOKEN_EXPIRE_HOURS)
payload = {
"sub": str(user_id),
"email": email,
"user_type": user_type,
"rol_operativo": rol_operativo,
"exp": expire,
"iat": datetime.utcnow()
}
return jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
def verify_token(self, token: str) -> dict:
try:
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expirado")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Token inválido")
def decode_token(self, token: str) -> dict:
try:
return jwt.decode(token, self.secret_key, algorithms=[self.algorithm], options={"verify_exp": False})
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Token inválido")
class CurrentUser:
"""Modelo del usuario autenticado"""
def __init__(self, id: int, email: str, user_type: str, rol_operativo: str):
self.id = id
self.email = email
self.user_type = user_type
self.rol_operativo = rol_operativo
self.ip_address = None
def is_root(self) -> bool:
return self.user_type == UserType.ROOT
def is_admin(self) -> bool:
return self.user_type == UserType.ADMIN
def is_admin_licencias(self) -> bool:
return self.user_type == UserType.ADMIN_LICENCIAS
def has_permission(self, required_type: str) -> bool:
"""Verifica si el usuario tiene el tipo requerido o superior"""
return UserType.has_permission(self.user_type, required_type)
def can_access_enterprise(self, enterprise_id: int, user_enterprise_id: int = None) -> bool:
"""Verifica si puede acceder a una empresa"""
if self.is_root():
return True
if user_enterprise_id and self.user_type == UserType.ADMIN:
return enterprise_id == user_enterprise_id
return False
async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security),
auth_service: AuthService = Depends(lambda: AuthService())
) -> CurrentUser:
"""Dependencia para obtener el usuario actual desde el token"""
token = credentials.credentials
payload = auth_service.verify_token(token)
user = CurrentUser(
id=int(payload.get("sub")),
email=payload.get("email"),
user_type=payload.get("user_type", UserType.USER),
rol_operativo=payload.get("rol_operativo", OperationalRole.OPERATIVO)
)
return user
def require_user_type(required_type: str):
"""Dependencia para requerir un tipo de usuario específico"""
async def dependency(current_user: CurrentUser = Depends(get_current_user)):
if not current_user.has_permission(required_type):
raise HTTPException(
status_code=403,
detail=f"Se requiere tipo de usuario: {required_type}"
)
return current_user
return dependency
def require_rol(required_rol: str):
"""Dependencia para requerir un rol operativo específico"""
async def dependency(current_user: CurrentUser = Depends(get_current_user)):
if current_user.rol_operativo != required_rol and not current_user.is_root():
raise HTTPException(
status_code=403,
detail=f"Se requiere rol operativo: {required_rol}"
)
return current_user
return dependency

14
app/core/base.py Normal file
View File

@@ -0,0 +1,14 @@
"""UNIFICACION DE LOS MODELOS XMA
Todos los modelos deben importarse para que alembic los detecte
"""
from database import Base
# Export target_metadata for Alembic
target_metadata = Base.metadata
# Lista explicita de modelos
__all__ = [
"Base",
"target_metadata",
]

224
app/core/baseRepository.py Normal file
View File

@@ -0,0 +1,224 @@
# core/base_repository.py
from typing import Generic, TypeVar, Type, Optional, List, Dict, Any
from datetime import datetime
from sqlalchemy.orm import Session, Query
from sqlalchemy import and_, desc
from database import Base
ModelType = TypeVar("ModelType", bound= Base)
class BaseRepository(Generic[ModelType]):
"""
Repositorio base con:
- CRUD completo
- Soft delete integrado
- Trazabilidad (created_by, updated_by, deleted_by)
- Métodos para root, admin, admin_licencias
"""
def __init__(self, db: Session, model: Type[ModelType]):
self.db = db
self.model = model
# ============================================
# QUERY BASE
# ============================================
def _base_query(self, include_deleted: bool = False) -> Query:
"""
Query base con filtro de soft delete
include_deleted=True: incluye registros eliminados
"""
query = self.db.query(self.model)
if not include_deleted:
query = query.filter(
self.model.deleted_at.is_(None),
self.model.is_active == True
)
return query
# ============================================
# CRUD BÁSICO
# ============================================
def get_by_id(self, id: int, include_deleted: bool = False) -> Optional[ModelType]:
"""Obtiene por ID"""
return self._base_query(include_deleted).filter(self.model.id == id).first()
def get_all(
self,
skip: int = 0,
limit: int = 100,
filters: Dict[str, Any] = None,
order_by: str = None,
descending: bool = False,
include_deleted: bool = False
) -> List[ModelType]:
"""Lista con paginación y filtros"""
query = self._base_query(include_deleted)
# Aplicar filtros
if filters:
for key, value in filters.items():
if hasattr(self.model, key) and value is not None:
query = query.filter(getattr(self.model, key) == value)
# Ordenamiento
if order_by and hasattr(self.model, order_by):
order_col = getattr(self.model, order_by)
query = query.order_by(desc(order_col) if descending else order_col)
return query.offset(skip).limit(limit).all()
def count(self, filters: Dict[str, Any] = None, include_deleted: bool = False) -> int:
"""Cuenta registros"""
query = self._base_query(include_deleted)
if filters:
for key, value in filters.items():
if hasattr(self.model, key) and value is not None:
query = query.filter(getattr(self.model, key) == value)
return query.count()
def create(self, data: Dict[str, Any], created_by: int = None) -> ModelType:
"""Crea un nuevo registro"""
import logging
logger = logging.getLogger(__name__)
try:
# Eliminar campos que deben ser automáticos
data.pop("created_at", None)
data.pop("updated_at", None)
data.pop("deleted_at", None)
data.pop("last_login", None)
data.pop("id", None)
if created_by:
data["created_by"] = created_by
logger.info(f"CREANDO ENTIDAD {self.model.__name__} CON DATOS: {data}")
entity = self.model(**data)
self.db.add(entity)
self.db.commit()
self.db.refresh(entity)
return entity
except Exception as e:
logger.error(f"ERROR EN CREATE: {str(e)}")
self.db.rollback()
raise e
def update(
self,
id: int,
data: Dict[str, Any],
updated_by: int = None,
include_deleted: bool = False
) -> Optional[ModelType]:
"""Actualiza un registro"""
entity = self.get_by_id(id, include_deleted)
if not entity:
return None
# No permitir actualizar si está eliminado
if entity.deleted_at is not None and not include_deleted:
return None
# Actualizar campos
for key, value in data.items():
if hasattr(entity, key) and value is not None:
setattr(entity, key, value)
if updated_by:
entity.updated_by = updated_by
self.db.commit()
self.db.refresh(entity)
return entity
def soft_delete(self, id: int, deleted_by: int = None) -> bool:
"""Soft delete: marca is_active=False y deleted_at"""
entity = self.get_by_id(id)
if not entity:
return False
if entity.deleted_at is not None:
return False
entity.is_active = False
entity.deleted_at = datetime.utcnow()
if deleted_by:
entity.deleted_by = deleted_by
self.db.commit()
return True
def hard_delete(self, id: int) -> bool:
"""Eliminación física"""
entity = self.get_by_id(id, include_deleted=True)
if not entity:
return False
self.db.delete(entity)
self.db.commit()
return True
def restore(self, id: int, updated_by: int = None) -> Optional[ModelType]:
"""Restaura un registro soft-deleted"""
entity = self.get_by_id(id, include_deleted=True)
if not entity:
return None
if entity.deleted_at is None:
return entity
entity.is_active = True
entity.deleted_at = None
if updated_by:
entity.updated_by = updated_by
self.db.commit()
self.db.refresh(entity)
return entity
# ============================================
# MÉTODOS PARA DIFERENCIACIÓN DE USUARIOS
# ============================================
# Estos métodos serán sobrescritos en repositorios específicos
# si el modelo tiene los campos correspondientes
def get_by_user_type(self, user_type: str) -> List[ModelType]:
"""Obtiene por tipo de usuario (root, admin, admin_licencias, user)"""
if hasattr(self.model, "tipo_usuario"):
return self._base_query().filter(self.model.tipo_usuario == user_type).all()
return []
def get_root(self) -> List[ModelType]:
"""Obtiene usuarios tipo root"""
return self.get_by_user_type("root")
def get_admin(self) -> List[ModelType]:
"""Obtiene usuarios tipo admin"""
return self.get_by_user_type("admin")
def get_admin_licencias(self) -> List[ModelType]:
"""Obtiene usuarios tipo admin_licencias"""
return self.get_by_user_type("admin_licencias")
#===============================================
# el tipo de usuario "PRIVILEGED" un esta por definir que clase de permisos tendras
#=================================
def get_by_role(self, role: str) -> List[ModelType]:
"""Obtiene por rol operativo"""
if hasattr(self.model, "rol_operativo"):
return self._base_query().filter(self.model.rol_operativo == role).all()
return []
def get_by_enterprise(self, enterprise_id: int) -> List[ModelType]:
"""Obtiene por empresa"""
if hasattr(self.model, "enterprise_id"):
return self._base_query().filter(self.model.enterprise_id == enterprise_id).all()
return []

0
app/core/dependencies.py Normal file
View File

0
app/core/intefaces.py Normal file
View File

64
app/core/security.py Normal file
View File

@@ -0,0 +1,64 @@
# core/security.py
from passlib.context import CryptContext
from datetime import datetime, timedelta
from typing import Optional
#
import os
import jwt
import logging
logger = logging.getLogger(__name__)
# Configuración de hashing de contraseñas
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Configuración de JWT
SECRET_KEY = os.getenv("SECRET_KEY", "xma-secret-key-change-in-production")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_HOURS = int(os.getenv("ACCESS_TOKEN_EXPIRE_HOURS", "1"))
def get_password_hash(password: str) -> str:
"""Genera hash de contraseña con bcrypt"""
if password.startswith("$2b$") or password.startswith("$2a$"):
raise ValueError("La contrasena ya esta hasheada")
password_bytes = password.encode('utf-8')
if len(password_bytes) > 72:
password = password[:72]
return pwd_context.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
try:
return pwd_context.verify(plain_password, hashed_password)
except Exception as e:
logger.error(f"Error verifying password: {e}")
return False
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
"""Crea token JWT"""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(hours=ACCESS_TOKEN_EXPIRE_HOURS)
to_encode.update({"exp": expire, "iat": datetime.utcnow()})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def decode_access_token(token: str) -> dict:
"""Decodifica token JWT"""
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except jwt.ExpiredSignatureError:
raise ValueError("Token expirado")
except jwt.InvalidTokenError:
raise ValueError("Token inválido")

91
app/core/servo.py Normal file
View File

@@ -0,0 +1,91 @@
# core/servo.py
"""
XMA Servomotor - Gestor simple del ciclo de vida
"""
import asyncio
import signal
import sys
from enum import Enum
from typing import Optional
from datetime import datetime
import logging
from database import test_connection, dispose_engine
logger = logging.getLogger(__name__)
class ServoState(str, Enum):
BOOT = "boot"
RUN = "run"
STOP = "stop"
class XMAServo:
"""
Servomotor simple que:
- Verifica BD al arrancar
- Ejecuta tareas en segundo plano si es necesario
- Maneja señales de apagado
"""
def __init__(self, mode: str = "dev"):
self.mode = mode
self.state = ServoState.BOOT
self.boot_time = datetime.utcnow()
self._tasks = []
self._running = False
async def start(self):
"""Arranca el servomotor"""
logger.info(f" XMA Servomotor iniciando (modo: {self.mode})")
# Verificar BD
if not test_connection():
logger.error(" Base de datos no disponible")
sys.exit(1)
logger.info(" Base de datos conectada")
self.state = ServoState.RUN
self._running = True
logger.info(f" XMA Servomotor en estado RUN")
# Si necesitas tareas en segundo plano, las agregas aquí
# await self._start_background_tasks()
async def stop(self):
"""Detiene el servomotor"""
logger.info(" Deteniendo servomotor...")
self.state = ServoState.STOP
self._running = False
# Cancelar tareas
for task in self._tasks:
task.cancel()
# Cerrar conexiones
dispose_engine()
logger.info(" Servomotor detenido")
def get_status(self) -> dict:
return {
"state": self.state.value,
"mode": self.mode,
"uptime": (datetime.utcnow() - self.boot_time).total_seconds(),
"database": "connected" if test_connection() else "disconnected"
}
# Instancia global
_servo = None
def get_servo(mode: str = None) -> XMAServo:
global _servo
if _servo is None:
import os
mode = mode or os.getenv("XMA_MODE", "dev")
_servo = XMAServo(mode=mode)
return _servo

52
app/modules/__init__.py Normal file
View File

@@ -0,0 +1,52 @@
"""
UNIFICACION DE LOS MODELOS XMA
Todos los modelos deben importarse para que alembic los detecte
"""
# Importar Base directamente desde database para evitar circular imports
from database import Base
# Importar todos los modelos
from app.modules.clients.models import Client
from app.modules.users.models import Users
from app.modules.affidavit_logs.models import AffidavitRecord
from app.modules.branches.models import Branches
from app.modules.coments.models import Coments
from app.modules.configuration.models import Configuration
from app.modules.credits.models import Credits
from app.modules.edos.models import EDOS
from app.modules.efos.models import EFOS
from app.modules.feed.models import Feed
from app.modules.files.models import Files
from app.modules.interactions.models import Interacction
from app.modules.invoices.models import Invoices
from app.modules.license.models import License
from app.modules.location.models import Locations
from app.modules.moves.models import Moves
from app.modules.suppliers.models import Suppliers
# Metadata para Alembic
target_metadata = Base.metadata
# Lista explícita de modelos XMA
__all__ = [
"Base",
"target_metadata",
"Users",
"Client",
"Locations",
"AffidavitRecord",
"Branches",
"Coments",
"Configuration",
"Credits",
"EDOS",
"EFOS",
"Feed",
"Interacction",
"Files",
"Invoices",
"License",
"Moves",
"Suppliers",
]

Binary file not shown.

View File

@@ -0,0 +1,58 @@
from sqlalchemy import Column, Integer, String, DateTime, Float, Boolean, Text, ForeignKey, Enum as SQLEnum, Index
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from database import Base
import enum
class StatusOP(str, enum.Enum):
COMPLETED = "completed"
PARTIAL = "partial"
INITIAL = "initial"
NO_PROCECED = "no_proceced"
INVALID = "invalid"
class Operation(str, enum.Enum):
DELETED ="deleted"
CREATED ="created"
UPDATED ="updated"
TRIED ="tried"
MOST ="mosted"
LESS ="less"
MINUS ="minus"
CANCELED ="canceled"
class AffidavitRecord(Base):
__tablename__ = "affidavit_logs"
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
operation = Column(SQLEnum(Operation, name="operation", create_type=False), default=Operation.TRIED, nullable=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
client_id = Column(Integer, ForeignKey("clients.id"), nullable=True)
description = Column(Text,nullable=False)
adjustment = Column(String(100), nullable=True)
status = Column(SQLEnum(StatusOP, name="status", create_type=False), default=StatusOP.INITIAL, nullable=True)
sign = Column(String, nullable=False)
#timestamsp
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)
deleted_at = Column(DateTime(timezone=True), nullable=True)
#trace
created_by = Column(Integer, ForeignKey("users.id"), nullable=True)
updated_by = Column(Integer, ForeignKey("users.id"), nullable=True)
deleted_by = Column(Integer, ForeignKey("users.id"), nullable=True)
#relationships
user = relationship("Users", foreign_keys=[user_id], back_populates="affidavit_logs")
client = relationship("Client", foreign_keys=[client_id])
__table_args__ = (
Index('idx_affidavit_user', 'user_id'),
Index('idx_affidavit_client', 'client_id'),
Index('idx_affidavit_operation', 'operation'),
Index('idx_affidavit_status', 'status'),
Index('idx_affidavit_created', 'created_at'),
)

View File

View File

View File

View File

@@ -0,0 +1,47 @@
from sqlalchemy import Column, Integer, String, DateTime, Float, Boolean,Index, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from database import Base
import enum
class Branches(Base):
__tablename__ = "branches"
id = Column(Integer,primary_key=True, nullable=False, autoincrement=True)
direccion = Column(String(255), nullable=False)
cp = Column(Integer, nullable=False)
#physical
is_physical = Column(Boolean, nullable=False)
location_id = Column(Integer, ForeignKey("location.id"), nullable=False)
#validation
is_active = Column(Boolean, nullable=False, default=True)
#cliente_id
client_id = Column(Integer, ForeignKey("clients.id"), nullable=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
#timestamsp
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)
deleted_at = Column(DateTime(timezone=True), nullable=True)
#trace
created_by = Column(Integer, nullable=True)
updated_by = Column(Integer, nullable=True)
deleted_by = Column(Integer, nullable=True)
#indices
__table_args__ = (
Index('idx_branches_client', 'client_id'),
Index('idx_branches_location', 'location_id'),
Index('idx_branches_user', 'user_id'),
Index('idx_branches_active', 'is_active')
)
#relaciones
client = relationship("Client", foreign_keys=[client_id], back_populates="branches")
location = relationship("Locations", foreign_keys=[location_id], back_populates="branches")
user = relationship("Users", back_populates="branch")

View File

View File

View File

Binary file not shown.

View File

@@ -0,0 +1,76 @@
from sqlalchemy import Column, Integer, String, DateTime, Float, Index, Boolean, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from database import Base
import enum
class Medio(str, enum.Enum):
MANUAL = "manual"
DIOT = "diot"
EXCEL = "excel"
FACTURA = "factura"
class Tercero(str, enum.Enum):
NACIONAL = "nacional"
EXTRANJERO = "extranjero"
GLOBAL = "global"
class Operacion(str, enum.Enum):
PRESTACIONSERVICIOSPROFECIONALES = "prestacion"
ARRENDAMIENTOSINMUENBLES = "arrendamientos"
OTROS = "otros"
class Client(Base):
__tablename__ = "clients"
#Identificacion
id = Column(Integer, primary_key=True, index=True)
rfc = Column(String(60), nullable=False, unique=True)
email = Column(String(150), nullable=False, unique=True)
#Datos fiscales
short_name = Column(String(255), nullable=True)
razon_social = Column(String(255), nullable=False, index=True)
fiscal_number = Column(String(15), nullable=False, unique=True)
cellphone = Column(String(20), nullable=False)
#clasififcacion
medio = Column(SQLEnum(Medio, name="medio_enum", create_type=False), default=Medio.MANUAL, nullable=False)
third_type = Column(SQLEnum(Tercero, name="tercero_enum", create_type=False), default=Tercero.GLOBAL, nullable=False)
operation_type = Column(SQLEnum(Operacion, name="operacion_enum", create_type=False), default=Operacion.OTROS, nullable=False)
is_foreign = Column(Boolean, nullable=True, default=False)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
location_id = Column(Integer, ForeignKey("location.id"), nullable=True)
#timestamps
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)
deleted_at = Column(DateTime(timezone=True), nullable=True)
#trazabilidad
created_by = Column(Integer, nullable=True)
updated_by = Column(Integer, nullable=True)
deleted_by = Column(Integer, nullable=True)
__table_args = (
Index('idx_clients_user', 'user_id'),
Index('idx_clients_location', 'location_id'),
)
#relationships
user = relationship("Users", foreign_keys=[user_id], back_populates="clients")
location = relationship("Locations", foreign_keys=[location_id])
license = relationship("License", back_populates="client")
branches = relationship("Branches", back_populates="client")
def __repr__(self):
return f"<Client(id={self.id}, rfc={self.rfc}, razon_social={self.razon_social})>"

View File

View File

View File

Binary file not shown.

View File

@@ -0,0 +1,37 @@
from sqlalchemy import Column, Integer, String, DateTime, Index, Float, Boolean, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from database import Base
import enum
class Coments(Base):
__tablename__= "coments"
id = Column(Integer, primary_key=True, nullable=False)
texto = Column(Text, nullable=False)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
feed_id= Column( Integer, ForeignKey("feed.id"), nullable=True)
is_active = Column(Boolean, nullable=False, default=True)
created_at = Column( DateTime(timezone=True), server_default=func.now(), nullable=False)
deactivate_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)
created_by = Column(Integer, ForeignKey("users.id"), nullable=False)
deactivated_by = Column(Integer, ForeignKey("users.id"), nullable=True)
#index
__table_args__ = (
Index('idx_coments_feed', 'feed_id'),
Index('idx_coments_user', 'user_id'),
Index('idx_coments_created', 'created_at'),
Index('idx_coments_active', 'is_active')
)
#Relaciones
user = relationship("Users", foreign_keys=[user_id], back_populates="coments")
feed = relationship("Feed", foreign_keys=[feed_id])
creator = relationship("Users", foreign_keys=[created_by])

View File

View File

View File

View File

View File

@@ -0,0 +1,34 @@
from sqlalchemy import Column, Integer, String, DateTime, Float, Boolean, Index, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from database import Base
import enum
class Configuration(Base):
__tablename__ = "configuration"
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
#SMTP data
smtp_host = Column(String(100), nullable=True)
smtp_port = Column(Integer, nullable=True)
smtp_user = Column(String(100), nullable=True)
smtp_password = Column(String(100), nullable=True)
is_active = Column(Boolean, nullable=False, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)
deleted_at = Column(DateTime(timezone=True), nullable=True)
last_send = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
created_by = Column(Integer, nullable=False)
updtated_by = Column(Integer, nullable=True)
deleted_by = Column(Integer, nullable=True)
__table_args__ = (
Index('idx_cofig_active', 'is_active'),
)

View File

View File

View File

Binary file not shown.

View File

@@ -0,0 +1,50 @@
from sqlalchemy import Column, Integer, String, Date, DateTime, Float, Boolean, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from database import Base
import enum
# Articulo 69 de codigo fiscal de la federacion!!!
# 69 article of fiscal code of federation, mexican united states.
class Supuestos(str, enum.Enum):
CANCELADOS ="cancelados"
CONDONADOS ="condonados"
FIRMES ="firmes"
SENTENCIAS ="sentencias"
EXIGIBLES ="exigibles"
RETORNO_INVERSIONES ="retorno_inversiones"
FRACCION_X ="fraccion_x"
FRACCION_VII ="fraccion_vii"
NO_LOCALIZADOS ="no_localizados"
class Credits(Base):
__tablename__="credits"
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
title = Column(String(30), nullable=False)
rfc = Column(String(25), nullable=False)
razon_social = Column(String(100), nullable=False)
tipo_persona = Column(String(100), nullable=False)
supuesto = Column(SQLEnum(Supuestos, name="supuestos", create_type=False), nullable=False)
fecha_primera_publicacion = Column(Date, nullable=False)
fecha_publicacion_ley_transparencia = Column(Date, nullable=True)
fecha_cancelacion = Column(Date, nullable=True)
fecha_cancelacion_csd = Column(Date, nullable=True)
entidad_federativa = Column(String, nullable=False)
monto = Column(Integer, nullable=True)
motivo = Column(Text, nullable=True)
location_id = Column(Integer, ForeignKey("location.id"), nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=True)
uploaded_by = Column(Integer, nullable=True)
# relationships
location = relationship("Locations", back_populates="credits")

View File

View File

View File

Binary file not shown.

View File

View File

View File

View File

Binary file not shown.

View File

@@ -0,0 +1,30 @@
from sqlalchemy import Column, Integer, String, Date, DateTime, Float, Boolean, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from database import Base
import enum
#articulo 69-B-Bis del CFF
# 69 article -B - Bis, of fiscal code of fedration of mexican united estates
class Situacion(str, enum.Enum):
SENTENCIA_FAVORABLE ="sentencia_favorable"
DEFINITIVO="definitivo"
class EDOS(Base):
__tablename__="edos"
id = Column(Integer, primary_key=True, nullable=False)
numero = Column(Integer, nullable=False)
razon_social = Column(String(100), nullable=False)
situacion = Column(SQLEnum(Situacion, name="situcion", create_type=False), nullable=False)
numero_definitivo = Column(String(60), nullable=False)
fecha_definitivo = Column(Date, nullable=False )
publicaccion_sat = Column(Date, nullable=True)
numero_def_dof = Column(String(100), nullable=True)
fecha_def_dof = Column(Date, nullable=True)
publicacion_dof = Column(Date, nullable=True)
numero_fav_sat = Column(String(100), nullable=True)

View File

View File

View File

Binary file not shown.

View File

@@ -0,0 +1,33 @@
from sqlalchemy import Column, Integer, String, Date, DateTime, Float, Boolean, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from database import Base
import enum
#Articulo 69-B del codigo fiscal de la federacion
#69-B article of fiscal code of federation of mexican united states
class Situacion(str, enum.Enum):
DEFINITIVO ="definitivo"
DESVIRTUADO ="desvituado"
PRESUNTO ="presunto"
SENTENCIA_FAVORABLE ="sentencia_favorable"
class EFOS(Base):
__tablename__ = "efos"
id = Column(Integer, primary_key=True, nullable=False)
numero = Column(Integer, nullable=False)
rfc = Column(String(30), nullable=False)
nombre_contribuyente = Column(String(100), nullable=False)
situacion = Column(String(60), SQLEnum(Situacion, name="situacion", create_type=False), nullable=False)
publi_presuntos_sat = Column(Date, nullable=True)
publi_desvirtuados_sat = Column(Date, nullable=True)
publi_definitivos_sat = Column(Date, nullable=True)
publi_favorables_sat = Column(Date, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=False)
loaded_by = Column(Integer, nullable=False)

View File

View File

View File

View File

View File

View File

View File

Binary file not shown.

View File

@@ -0,0 +1,26 @@
from sqlalchemy import Column, Integer, String, Date, DateTime, Float, Boolean, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from database import Base
import enum
class Feed(Base):
__tablename__ = "feed"
id = Column(Integer, primary_key=True, nullable=False)
title = Column(String(50), nullable=False )
body = Column(Text, nullable=True)
document_url = Column(String, nullable=True)
publication_date = Column(Date, nullable=True)
is_important = Column(Boolean, default=False, nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
created_by = Column(Integer, ForeignKey("users.id"), nullable=True)
updated_by = Column(Integer, ForeignKey("users.id"), nullable=True)
file_id = Column(Integer, nullable=True)
#relationships
user = relationship("Users", foreign_keys=[user_id], back_populates="feed")

View File

View File

View File

Binary file not shown.

View File

@@ -0,0 +1,24 @@
from sqlalchemy import Column, Integer, String, Date, DateTime, Float, Boolean, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from database import Base
import enum
class Files(Base):
__tablename__ = "files"
id = Column(Integer, primary_key=True, nullable=False)
title = Column(String(50), nullable=True)
summary = Column(Text, nullable=False)
document_url = Column(String(100), nullable=False)
file_type = Column(String(50), nullable=False)
file_size = Column(Integer, nullable=False)
feed_id = Column(Integer, nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=False )
uploaded_by = Column(Integer, nullable=False)
updated_by = Column(Integer, nullable=True)

View File

View File

View File

View File

@@ -0,0 +1,34 @@
from sqlalchemy import Column, Integer, String, Date, DateTime, Float, Boolean, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from database import Base
import enum
class Interaccion(str, enum.Enum):
LIKE = "like"
DONT_LIKE = "dont_like"
APPROVED = "approved"
DISAPRROVE = "disapproved"
NONE = "none"
class Interacction(Base):
__tablename__="interacctions"
id = Column(Integer, primary_key=True, nullable=False)
feed_id = Column(Integer, ForeignKey("feed.id"), nullable=False)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
type_iteractions = Column(SQLEnum(Interaccion, name="type_interactios", create_type=False), default=Interaccion.NONE, nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)
#relationships
user = relationship("Users", back_populates="interactions")

View File

View File

View File

View File

@@ -0,0 +1,23 @@
from sqlalchemy import Column, Integer, String, Date, DateTime, Float, Boolean, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from database import Base
import enum
class Invoices(Base):
__tablename__ = "invoices"
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
emisor = Column(String(100), nullable=False)
receptor = Column(String(110), nullable=False)
uuid = Column(String(255), nullable=False)
total = Column(Integer, nullable=False)
tipo = Column(String(50), nullable=True)
date = Column(DateTime, nullable=True)
verified_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
verified_by = Column(Integer, nullable=True)

View File

View File

View File

Binary file not shown.

View File

@@ -0,0 +1,45 @@
from sqlalchemy import Column, Integer, String, DateTime, Index, Float, Boolean, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from database import Base
import enum
class License(Base):
__tablename__ = "license"
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
titular = Column(Integer, ForeignKey("users.id"), nullable=False)
begins_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
ends_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
token_license = Column(String(30), nullable=True)
location_id = Column(Integer, ForeignKey("location.id"), nullable=False)
client_id = Column(Integer, ForeignKey("clients.id"), nullable=False)
#timestamsp
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)
deleted_at = Column(DateTime(timezone=True), nullable=False)
#trace
created_by = Column(Integer, nullable=True)
updated_by = Column(Integer, nullable=True)
deleted_by = Column(Integer, nullable=True)
__table_arg__ = (
Index('idx_license_token', 'token_license'),
Index('idx_license_client', 'client_id'),
Index('idx_license_active', 'is_active'),
Index('idx_license_ends_at', 'ends_at'),
)
#Relations
client = relationship("Client", foreign_keys=[client_id], back_populates="license")
location = relationship("Locations", foreign_keys=[location_id], back_populates="licenses")
user = relationship("Users", foreign_keys=[titular], back_populates="licenses")

View File

View File

View File

View File

@@ -0,0 +1,50 @@
from sqlalchemy import Column, Integer, String, DateTime, Index, Float, Boolean, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from database import Base
import enum
class Locations(Base):
__tablename__ = "location"
id = Column(Integer, primary_key=True, nullable=False)
country = Column(String(120), nullable=False)
country_id = Column(Integer, nullable=False)
state = Column(String(100), nullable=False)
state_id = Column(Integer, nullable=False)
city = Column(String(100), nullable=False)
city_id = Column(Integer, nullable=False)
cp_zp = Column(Integer, nullable=True)
street = Column(String(120), nullable=True)
is_department = Column(Boolean, nullable=False, default=False)
number_ext = Column(Integer, nullable=True)
number_int = Column(Integer, nullable=True)
is_active = Column(Boolean, default=True, nullable=False)
#timestamsp
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)
deleted_at = Column(DateTime, nullable=True)
#trace
created_by = Column(Integer, nullable=True)
updated_by = Column(Integer, nullable=True)
deleted_by = Column(Integer, nullable=True)
#index
__table_args__ = (
Index('idx_location_country','country'),
Index('idx_location_state','state'),
Index('idx_location_city','city'),
Index('idx_location_cp','cp_zp'),
Index('idx_location_active','is_active'),
)
#relationships
clients = relationship("Client",back_populates="location")
branches = relationship("Branches",back_populates="location")
credits = relationship("Credits",back_populates="location")
licenses = relationship("License",back_populates="location")

View File

View File

View File

View File

Binary file not shown.

View File

@@ -0,0 +1,71 @@
from sqlalchemy import Column, Integer, String, Date, Index, DateTime, Float, Boolean, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from database import Base
from sqlalchemy.dialects.postgresql import JSONB
import enum
class Action(str, enum.Enum):
UPLOAD = "upload"
MATCH = "match"
QUERY = "query"
CONSUMPTION = "consumption"
CREATE = "create"
DELETE = "delete"
SOLD = "sold"
INTERACTION = "interaction"
COMMENT = "comment"
CONFIG = "config"
EMAIL = "email"
class Target(str, enum.Enum):
CLIENT = "client"
INVOICES = "invoices"
FEED = "feed"
EFOS = "efos"
CREDITS = "credits"
USERS = "users"
EMAIL = "email"
class Moves(Base):
__tablename__ = "moves"
id = Column(Integer, primary_key=True, nullable=False)
action_type = Column(SQLEnum(Action, name="action_type", create_type=False), nullable=False)
target_type = Column(SQLEnum(Target, name="target_type", create_type=False), nullable=False)
description = Column(Text, nullable=True)
move_metadata = Column(JSONB, nullable=True)
ip_address = Column(String(45), nullable=True)
user_agent = Column(String(255), nullable=True)
client_id = Column(Integer, ForeignKey("clients.id"), nullable=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
is_active = Column(Boolean, nullable=False, default=True)
#relationships
user = relationship("Users", foreign_keys=[user_id], back_populates="moves")
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
created_by = Column(Integer, ForeignKey("users.id"), nullable=True)
updated_at = Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)
deleted_by = Column(Integer, ForeignKey("users.id"), nullable=True)
__table_args_ = (
Index('idx_moves_client','client_id'),
Index('idx_moves_user','user_id'),
Index('idx_moves_action','action_type'),
Index('idx_moves_target','target_type'),
Index('idx_moves_created','created_at'),
Index('idx_moves_active','is_active'),
)
#Relaciones
client = relationship("Client", foreign_keys=[client_id])
creator = relationship("Users", foreign_keys=[created_by])

Some files were not shown because too many files have changed in this diff Show More