[BE-01] feat: Implementação de CRUD da tela de Caixa

This commit is contained in:
Keven Willian 2025-06-30 08:56:33 -03:00
commit f353cf630f
42 changed files with 1230 additions and 0 deletions

9
Api/.gitattributes vendored Normal file
View file

@ -0,0 +1,9 @@
# Normaliza finais de linha
* text=auto
# Força Python e arquivos de configuração a usarem LF
*.py text eol=lf
*.sh text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.env text eol=lf

38
Api/.gitignore vendored Normal file
View file

@ -0,0 +1,38 @@
# Ambiente virtual
venv/
.env
.env.*
# Bytecode compilado
__pycache__/
*.py[cod]
*$py.class
# Arquivos temporários do sistema
.DS_Store
Thumbs.db
# Logs e databases locais
*.log
*.sqlite3
# VSCode
.vscode/
# PyCharm
.idea/
# Arquivos de testes ou builds
*.coverage
htmlcov/
coverage.xml
dist/
build/
.eggs/
*.egg-info/
# Cache do pip
pip-wheel-metadata/
*.egg
.cache/
.tox/

26
Api/Dockerfile Normal file
View file

@ -0,0 +1,26 @@
# Usa a imagem oficial do Python
FROM python:3.12-slim
# Define diretório de trabalho no container
WORKDIR /app
# Copia o arquivo de dependências
COPY requirements.txt .
# Instala dependências no sistema e no Python
RUN apt-get update && apt-get install -y \
gcc libffi-dev libssl-dev python3-dev firebird-dev \
&& pip install --upgrade pip \
&& pip install --no-cache-dir -r requirements.txt \
&& apt-get remove -y gcc \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
# Copia o restante do projeto para o container
COPY . .
# Expõe a porta padrão do Uvicorn/FastAPI
EXPOSE 8000
# Comando para iniciar o servidor
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

BIN
Api/FBCLIENT.DLL Normal file

Binary file not shown.

26
Api/README.md Normal file
View file

@ -0,0 +1,26 @@
# 🧾 MyDocs - Gerador de Documentos
Aplicação web para gerenciamento de empresas e geração automatizada de documentos com base em **minutas personalizadas** e **informações cadastradas**.
## 🚀 Funcionalidades
### ✅ Concluídas:
- **Autenticação de Usuário**
- Sistema de login seguro
- **CRUD de Empresas**
- Cadastro, edição, visualização e exclusão de empresas
### 🛠️ Em Desenvolvimento:
- **Gerador de Documentos**
- Geração dinâmica de documentos com base em minutas e dados de empresas
- **Cadastro de Minutas**
- Sistema para criação e gerenciamento de templates/minutas de documentos
- **Cadastro de Produtos**
- Cadastro de produtos vinculados a empresas ou documentos
## 🧩 Tecnologias Utilizadas
- **Backend:** Python / FastApi
- **Frontend:** React / NextJs
- **Banco de Dados:** MySQL
- **Autenticação:** JWT

13
Api/api/v1/api.py Normal file
View file

@ -0,0 +1,13 @@
# Importa o gerenciador de rotas do FastAPI
from fastapi import APIRouter
# Importa os módulos de rotas específicos
from api.v1.packages.administrative.endpoints import c_caixa_item
# Cria uma instância do APIRouter que vai agregar todas as rotas da API
api_router = APIRouter()
# Inclui as rotas de caixa
api_router.include_router(
c_caixa_item.router, prefix="/administrativo/caixa", tags=["Caixa"]
)

View file

@ -0,0 +1,13 @@
from core.base.base_action import BaseAction
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
from api.v1.packages.administrative.repositories.c_caixa_item.delete import Delete
class DeleteAction(BaseAction):
def execute(self, caixa_item_schema : CaixaItemSchema):
# Instânciamento de repoistório
delete = Delete()
# Retorna o resultado da operação
return delete.execute(caixa_item_schema)

View file

@ -0,0 +1,12 @@
from core.base.base_action import BaseAction
from api.v1.packages.administrative.repositories.c_caixa_item.index import Index
class IndexAction(BaseAction):
def execute(self):
# Instânciamento de repositório
index = Index()
# Retorna todos produtos
return index.execute()

View file

@ -0,0 +1,13 @@
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
from api.v1.packages.administrative.repositories.c_caixa_item.create import Create
from core.base.base_action import BaseAction
class SaveAction(BaseAction):
def execute(self, caixa_item_schema : CaixaItemSchema):
# Instância o repositório desejado
create = Create()
# Executa o respositório desejado
return create.execute(caixa_item_schema)

View file

@ -0,0 +1,13 @@
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
from api.v1.packages.administrative.repositories.c_caixa_item.show import Show
from core.base.base_action import BaseAction
class ShowAction(BaseAction):
def execute(self, caixa_item_schema : CaixaItemSchema):
# Instânciamento do repositório
show = Show()
# Retorna os dados localizados
return show.execute(caixa_item_schema)

View file

@ -0,0 +1,47 @@
# Importação de bibliotecas
from core.utils.dynamic_import import DynamicImport
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
class CCaixaItemController:
def index(self):
# Importação da classe desejad
indexService = DynamicImport.service("administrative", "c_caixa_item", "index_service", "IndexService")
# Intânciamento da classe service
self.indexService = indexService()
# Lista todos os produtos
return self.indexService.execute()
def create(self, caixa_item_schema: CaixaItemSchema):
# Importação da classe desejada
createService = DynamicImport.service("administrative", "c_caixa_item", "save_service", "SaveService")
# Intânciamento da classe service
self.createService = createService()
# Lista todos os produtos
return self.createService.execute(caixa_item_schema)
def show(self, caixa_item_schema: CaixaItemSchema):
# Importação da classe desejad
showService = DynamicImport.service("administrative", "c_caixa_item", "show_service", "ShowService")
# Intânciamento da classe service
self.showService = showService()
# Lista todos os produtos
return self.showService.execute(caixa_item_schema)
def delete(self, caixa_item_schema: CaixaItemSchema):
# Importação da classe desejad
deleteService = DynamicImport.service("administrative", "c_caixa_item", "delete_service", "DeleteService")
# Intânciamento da classe service
self.deleteService = deleteService()
# Lista todos os produtos
return self.deleteService.execute(caixa_item_schema)

View file

@ -0,0 +1,59 @@
# Importação de bibliotecas
from fastapi import APIRouter, status, Depends
from api.v1.packages.administrative.controllers.c_caixa_item_controller import CCaixaItemController
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
# Inicializar o roteaodr para as rotas de produtos
router = APIRouter()
# Instãnciamento do controller desejado
cCaixaItemController = CCaixaItemController()
@router.get("/", status_code=status.HTTP_200_OK)
async def index():
# Busca todos os produtos cadastrados
response = cCaixaItemController.index()
# Retornar os dados localizados
return {
"data": response
}
@router.post('/', status_code=status.HTTP_201_CREATED)
async def save(caixa_item_schema: CaixaItemSchema):
# Salva o produto desejado
response = cCaixaItemController.create(caixa_item_schema)
# Retorna a informação desejada
return {
"data": response
}
@router.get('/{caixa_item_id}', status_code=status.HTTP_200_OK)
async def show(caixa_item_id : int):
# Armazena o produto id no Schema
CaixaItemSchema.caixa_item_id = caixa_item_id
# Salva o produto desejado
response = cCaixaItemController.show(CaixaItemSchema)
# Retorna a informação desejada
return {
"data": response
}
@router.delete('/{caixa_item_id}', status_code=status.HTTP_200_OK)
async def delete(caixa_item_id : int):
# Armazena o produto id no Schema
CaixaItemSchema.caixa_item_id = caixa_item_id
# Salva o produto desejado
response = cCaixaItemController.delete(CaixaItemSchema)
# Retorna a informação desejada
return {
"data": response
}

View file

@ -0,0 +1,29 @@
# Importação de bibliotecas
from core.base.base_repository import BaseRepository
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
class Create(BaseRepository):
def execute(self, caixa_item : CaixaItemSchema):
# Realiza a inserção do registro
self.cursor.execute(
"""
INSERT INTO C_CAIXA_ITEM (
CAIXA_ITEM_ID,
CAIXA_SERVICO_ID
) VALUES (
?,
?
);
""",
(
caixa_item.caixa_item_id, caixa_item.caixa_servico_id,
),
)
# Comita a transação
self.commit()
# Retorna como verdadeiro se for salvo com sucesso
return True

View file

@ -0,0 +1,14 @@
from core.base.base_repository import BaseRepository
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
class Delete(BaseRepository):
def execute(self, caixa_item : CaixaItemSchema):
# Realiza a remoção
self.cursor.execute(""" DELETE FROM c_caixa_item cci WHERE cci.caixa_item_id = ?""", (caixa_item.caixa_item_id,))
# Comita a transação
self.commit()
return True

View file

@ -0,0 +1,14 @@
# Importação de bibliotecas
from core.base.base_repository import BaseRepository
class Index(BaseRepository):
def execute(self):
self.cursor.execute("""SELECT * FROM c_caixa_item cci""")
columns = [col[0] for col in self.cursor.description] # lista dos nomes das colunas
results = []
for row in self.cursor.fetchall():
results.append({columns[i]: row[i] for i in range(len(columns))})
self.commit()
return results

View file

@ -0,0 +1,21 @@
from core.base.base_repository import BaseRepository
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
class Show(BaseRepository):
def execute(self, caixa_item_schema: CaixaItemSchema):
# Executa a busca pelo ID usando placeholder correto (?)
self.cursor.execute(
"""SELECT * FROM c_caixa_item cci WHERE cci.caixa_item_id = ?""",
(caixa_item_schema.caixa_item_id,) # Importante: precisa ser tupla!
)
row = self.cursor.fetchone()
if not row:
return None
# Transforma em dict associativo
columns = [desc[0].lower() for desc in self.cursor.description]
return dict(zip(columns, row))

View file

@ -0,0 +1,52 @@
from typing import Optional
from pydantic import BaseModel
from datetime import date, datetime
class CaixaItemSchema(BaseModel):
especie_pagamento: Optional[str] = None
caixa_item_id: Optional[int] = None
caixa_servico_id: Optional[int] = None
usuario_servico_id: Optional[int] = None
usuario_caixa_id: Optional[int] = None
chave_servico: Optional[str] = None
descricao: Optional[str] = None
data_pagamento: Optional[date] = None
situacao: Optional[str] = None
tipo_documento: Optional[str] = None
tipo_transacao: Optional[str] = None
valor_servico: Optional[float] = None
valor_pago: Optional[float] = None
observacao: Optional[str] = None
caixa_cheque_id: Optional[int] = None
hora_pagamento: Optional[datetime] = None
caixa_id: Optional[int] = None
recibo_id: Optional[int] = None
tipo_servico: Optional[str] = None
qtd: Optional[int] = None
apresentante: Optional[str] = None
mensalista_id: Optional[int] = None
quitado_caixa_id: Optional[int] = None
registrado: Optional[bool] = None
emolumento: Optional[float] = None
taxa_judiciaria: Optional[float] = None
fundesp: Optional[float] = None
desconto: Optional[float] = None
valor_documento: Optional[float] = None
outra_taxa1: Optional[float] = None
chave_servico_sec: Optional[str] = None
emolumento_item_id: Optional[int] = None
caixa_registroselo_id: Optional[int] = None
fundo_ri: Optional[float] = None
valor_recibo: Optional[float] = None
boleto_pdf: Optional[str] = None # ou `bytes` se for binário
boleto_vencimento: Optional[date] = None
iss: Optional[float] = None
nlote: Optional[int] = None
tabela: Optional[str] = None
campo_id: Optional[int] = None
boleto_id: Optional[int] = None
valor_adicional: Optional[float] = None
pix_id: Optional[int] = None
class Config:
from_attributes = True

View file

@ -0,0 +1,28 @@
from fastapi import HTTPException, status
from api.v1.packages.administrative.actions.c_caixa_item.delete_action import DeleteAction
from api.v1.packages.administrative.actions.c_caixa_item.show_action import ShowAction
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
class DeleteService:
def execute(self, caixa_item_schema: CaixaItemSchema):
# Instânciamento de classe
showAction = ShowAction()
# Obtem o registro desejado
data = showAction.execute(caixa_item_schema)
# Verifica se o registro não existe
if not data:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Não foi possível localizar o registro'
)
# Instânciamento de ações
deleteAction = DeleteAction()
# Retorna todos produtos desejados
return deleteAction.execute(caixa_item_schema)

View file

@ -0,0 +1,11 @@
from api.v1.packages.administrative.actions.c_caixa_item.index_action import IndexAction
class IndexService:
def execute(self):
# Instânciamento de ações
indexAction = IndexAction()
# Retorna todos produtos desejados
return indexAction.execute()

View file

@ -0,0 +1,13 @@
from api.v1.packages.administrative.actions.c_caixa_item.save_action import SaveAction
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
class SaveService:
def execute(self, caixa_item_schema: CaixaItemSchema):
# Instânciamento de ações
saveAction = SaveAction()
# Retorna todos produtos desejados
return saveAction.execute(caixa_item_schema)

View file

@ -0,0 +1,25 @@
from fastapi import HTTPException, status
from api.v1.packages.administrative.actions.c_caixa_item.show_action import ShowAction
from api.v1.packages.administrative.schemas.c_caixa_item_schema import CaixaItemSchema
class ShowService:
def execute(self, caixa_item_schema: CaixaItemSchema):
# Instânciamento de ações
showAction = ShowAction()
# Retorna todos produtos desejados
data = showAction.execute(caixa_item_schema)
# Verifica se foi localizado o registro
if not data:
# Retorna uma exceção
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Não foi possível localizar o registro'
)
# Retorna a informação localizada
return data

10
Api/config/database.json Normal file
View file

@ -0,0 +1,10 @@
{
"firebird": {
"host": "localhost",
"name": "C:\\Users\\keven\\OneDrive\\Desktop\\Orius\\CALCILANDIA.FDB",
"port": 3050,
"user": "SYSDBA",
"password": "masterkey",
"charset": "UTF8"
}
}

34
Api/core/auth.py Normal file
View file

@ -0,0 +1,34 @@
from pytz import timezone
from datetime import datetime, timedelta
from fastapi.security import OAuth2PasswordBearer
from jose import jwt
from core.configs import settings
# Define o esquema OAuth2 para login
oauth2_schema = OAuth2PasswordBearer(
tokenUrl=f"{settings.API_V1_STR}/usuarios/login"
)
# Função para criar um token JWT
def create_token(tipo_token: str, tempo_vida: timedelta, sub: str) -> str:
payload = {}
sp = timezone('America/Sao_Paulo')
expira = datetime.now(tz=sp) + tempo_vida
payload["type"] = tipo_token
payload["exp"] = expira
payload["iat"] = datetime.now(tz=sp)
payload["sub"] = str(sub)
return jwt.encode(payload, settings.JWT_SECRET, algorithm=settings.ALGORITHM)
# Criação do token de acesso (access_token)
def create_access_token(sub: str) -> str:
return create_token(
tipo_token='access_token',
tempo_vida=timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES),
sub=sub
)

View file

@ -0,0 +1,16 @@
from abc import ABC, abstractmethod
class BaseAction:
"""
Classe abstrata base para todos as actions do sistema.
Obriga implementação de um método execute().
"""
@abstractmethod
def execute(self, *args, **kwargs):
"""
Método abstrato obrigatório a ser implementado pelas subclasses.
Deve conter a lógica principal do repositório.
"""
pass

View file

@ -0,0 +1,43 @@
from abc import ABC, abstractmethod
from core.database import get_connection
class BaseRepository(ABC):
"""
Classe abstrata base para todos os repositórios do sistema.
Fornece conexão com o banco de dados e obriga implementação de um método execute().
"""
def __init__(self):
"""
Inicializa a conexão e o cursor do banco de dados.
Essa conexão deve ser usada pelas subclasses.
"""
self.conn = get_connection()
self.cursor = self.conn.cursor()
@abstractmethod
def execute(self, *args, **kwargs):
"""
Método abstrato obrigatório a ser implementado pelas subclasses.
Deve conter a lógica principal do repositório.
"""
pass
def commit(self):
"""
Realiza o commit da transação.
"""
self.conn.commit()
def rollback(self):
"""
Realiza o rollback da transação.
"""
self.conn.rollback()
def close(self):
"""
Fecha cursor e conexão.
"""
self.cursor.close()
self.conn.close()

26
Api/core/configs.py Normal file
View file

@ -0,0 +1,26 @@
from pydantic_settings import BaseSettings
# Classe de configurações globais da aplicação
class Settings(BaseSettings):
# Prefixo base das rotas da API
API_V1_STR: str = '/api/v1'
# URL de conexão com o banco MySQL
# Formato: mysql://usuario:senha@host:porta/banco
DB_URL: str = "mysql://root:root@127.0.0.1:3306/mydocs"
# Chave secreta para geração dos tokens JWT
JWT_SECRET: str = 'WYe1zwtlDkh39_X3X3qTSICFDxts4VQrMyGLxnEpGUg'
# Algoritmo usado para assinar o token
ALGORITHM: str = 'HS256'
# Tempo de expiração do token JWT (em minutos): 1 semana
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7
# Configuração interna do Pydantic
class Config:
case_sensitive = True # Respeita letras maiúsculas/minúsculas nas variáveis de ambiente
# Instância única das configurações
settings: Settings = Settings()

22
Api/core/database.py Normal file
View file

@ -0,0 +1,22 @@
import fdb
from core.utils.config import Config
def get_connection():
"""
Constrói e retorna uma conexão com o banco MySQL
utilizando os dados da URL definida nas configurações.
"""
# Obtem as configurações de banco de dados
database = Config.get()
# Constrói o DSN no formato 'hostname/port:database_path'
# E essa string é passada como o PRIMEIRO ARGUMENTO POSICIONAL
connection_dsn = f"{database.firebird.host}/{database.firebird.port}:{database.firebird.name}"
return fdb.connect(
connection_dsn, # Este é o DSN completo que o driver espera
user=database.firebird.user,
password=database.firebird.password,
charset=database.firebird.charset
)

64
Api/core/deps.py Normal file
View file

@ -0,0 +1,64 @@
# core/deps.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import jwt, JWTError
from core.configs import settings
from api.v1.packages.users.models.users.users_model import UserModel # <--- Importe o UserModel
# Define o esquema de segurança OAuth2 (token tipo Bearer)
oauth2_schema = OAuth2PasswordBearer(
tokenUrl=f"{settings.API_V1_STR}/usuarios/login"
)
# Função que retorna o usuário autenticado com base no token JWT
def get_current_user(token: str = Depends(oauth2_schema)) -> dict:
credential_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Could not validate credentials',
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(
token,
settings.JWT_SECRET,
algorithms=[settings.ALGORITHM],
options={"verify_aud": False}
)
user_id: str = payload.get("sub")
if user_id is None:
raise credential_exception
except JWTError:
raise credential_exception
# --- NOVO: Buscar os dados completos do usuário do banco de dados ---
# Convert user_id para int, se ele for um string no JWT e int no banco
try:
user_id_int = int(user_id)
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid user ID format in token."
)
# Use o UserModel para buscar os dados completos
# Adicione um try-except para a chamada ao modelo para capturar erros de DB
try:
user = UserModel.get_by_id(user_id_int)
except Exception as e: # Captura qualquer erro ao buscar no DB
print(f"Error fetching user in get_current_user: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to retrieve user data. {str(e)}"
)
if not user:
# Se o usuário não for encontrado no DB (mas o token era válido para um ID),
# pode indicar um usuário deletado ou um ID inválido no token.
raise credential_exception # Ou HTTPException(404, "User associated with token not found")
return user # Retorna o dicionário completo do usuário

31
Api/core/security.py Normal file
View file

@ -0,0 +1,31 @@
# core/security.py
from passlib.context import CryptContext # Contexto de criptografia de senhas
from passlib.exc import UnknownHashError # Exceção para hashes não reconhecidos
# Define contexto de criptografia com esquema bcrypt
CRYPTO = CryptContext(schemes=['bcrypt'], deprecated='auto')
def verify_senha_api(plain_senha_api: str, hashed_senha_api: str) -> bool:
"""
Compara a senha fornecida em texto puro com o hash armazenado.
Retorna False em caso de erro ou formato inválido.
"""
try:
if not plain_senha_api or not hashed_senha_api:
return False # Garante que nenhum dos valores seja nulo ou vazio
# Verifica se a senha corresponde ao hash
return CRYPTO.verify(plain_senha_api, hashed_senha_api)
except UnknownHashError:
return False # Hash inválido ou não reconhecido pelo passlib
except Exception:
return False # Falha genérica na verificação
def hash_senha_api(plain_senha_api: str) -> str:
"""
Gera o hash da senha em texto puro.
"""
return CRYPTO.hash(plain_senha_api) # Retorna a senha criptografada com bcrypt

View file

@ -0,0 +1,4 @@
# exceptions.py
class BusinessRuleException(Exception):
def __init__(self, message: str):
self.message = message

View file

@ -0,0 +1,42 @@
# handlers.py
import traceback
from fastapi import Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
from core.system.exceptions import BusinessRuleException
def register_exception_handlers(app):
@app.exception_handler(BusinessRuleException)
async def business_rule_exception_handler(request: Request, exc: BusinessRuleException):
return JSONResponse(
status_code=422,
content={"error": "Regra de negócio", "detail": exc.message}
)
@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
return JSONResponse(
status_code=exc.status_code,
content={"error": "HTTP Error", "detail": exc.detail}
)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=400,
content={"error": "Erro de validação", "detail": exc.errors()}
)
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
return JSONResponse(
status_code=500,
content={
"error": "Erro Interno do Servidor",
"type": type(exc).__name__,
"message": str(exc),
"trace": traceback.format_exc()
}
)

8
Api/core/utils/cep.py Normal file
View file

@ -0,0 +1,8 @@
class CEP:
@staticmethod
def validate(data: str) -> bool:
# Valida e retorna a informação
return len(data) == 8

34
Api/core/utils/cnpj.py Normal file
View file

@ -0,0 +1,34 @@
import re
class CNPJ:
@staticmethod
def validate(data: str) -> bool:
# Remove caracteres não numéricos
data = re.sub(r'\D', '', data)
# Verifica se tem 14 dígitos
if len(data) != 14:
return False
# CNPJs com todos os dígitos iguais são inválidos
if data == data[0] * 14:
return False
# Calcula os dois dígitos verificadores
def calcular_digito(data, peso):
soma = sum(int(a) * b for a, b in zip(data, peso))
resto = soma % 11
return '0' if resto < 2 else str(11 - resto)
# Primeiro dígito verificador
peso1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
digito1 = calcular_digito(data[:12], peso1)
# Segundo dígito verificador
peso2 = [6] + peso1
digito2 = calcular_digito(data[:12] + digito1, peso2)
# Verifica se os dígitos batem
return data[-2:] == digito1 + digito2

20
Api/core/utils/config.py Normal file
View file

@ -0,0 +1,20 @@
import json
from types import SimpleNamespace
from pathlib import Path
class Config:
@staticmethod
def get():
# Caminho absoluto do arquivo atual
base_dir = Path(__file__).resolve().parent
# Caminho absoluto para o config.json (subindo dois níveis e entrando em config/)
config_path = base_dir.parent.parent / 'config' / 'database.json'
# Carrega o JSON como objeto acessível por ponto
with open(config_path, 'r') as f:
config = json.load(f, object_hook=lambda d: SimpleNamespace(**d))
return config

View file

@ -0,0 +1,17 @@
import importlib
class DynamicImport:
state = "go"
base = "api.v1.packages"
@staticmethod
def service(package: str, table: str, name: str, class_name : str):
try:
module_file = f"{name}"
path = f"{DynamicImport.base}.{package}.services.{table}.{DynamicImport.state}.{module_file}"
module = importlib.import_module(path)
clazz = getattr(module, class_name)
return clazz
except (ImportError, AttributeError) as e:
raise ImportError(f"Erro ao importar '{class_name}' de '{path}': {e}")

10
Api/core/utils/email.py Normal file
View file

@ -0,0 +1,10 @@
import re
class Email:
@staticmethod
def validate(data: str) -> str:
# Validação de email
default = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
if not re.match(default, data):
raise f"Email inválido: {data}"

12
Api/core/utils/phone.py Normal file
View file

@ -0,0 +1,12 @@
class Phone:
@staticmethod
def validate_cellphone(data: str) -> bool:
# Verifica e retorna se o numero de celular é igual a 11
return len(data) == 11
@staticmethod
def validate_telephone(data: str) -> bool:
# Verifica e retorna se o numero de telefone é igual a 11
return len(data) == 10

18
Api/core/utils/text.py Normal file
View file

@ -0,0 +1,18 @@
import re
import html
class Text:
@staticmethod
def sanitize(data: str) -> str:
"""Trim spaces, escape HTML entities, collapse multiple spaces."""
data = data.strip()
data = html.escape(data)
data = re.sub(r"\s+", " ", data)
return data
@staticmethod
def just_numbers(data: str) -> str:
""" Mantêm apenas os numeros """
data = re.sub(r"[^\d]", "", data)
return data

31
Api/core/validation.py Normal file
View file

@ -0,0 +1,31 @@
# utils/validation.py
import re
import html
class InputSanitizer:
@staticmethod
def clean_text(text: str) -> str:
"""Trim spaces, escape HTML entities, collapse multiple spaces."""
text = text.strip()
text = html.escape(text)
text = re.sub(r"\s+", " ", text)
return text
@staticmethod
def is_valid_email(email: str) -> bool:
"""Check if email has a valid structure"""
return bool(re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email))
@staticmethod
def has_script(text: str) -> bool:
"""Detect basic XSS attempts"""
return "<script" in text.lower() or "javascript:" in text.lower()
@staticmethod
def is_safe(text: str) -> bool:
"""Detect common XSS/SQL injection characters or patterns"""
blacklist = ["<script", "javascript:", "--", ";", "/*", "*/", "@@", "char(", "nchar(", "varchar(", "alter", "drop", "exec"]
text_lower = text.lower()
return not any(p in text_lower for p in blacklist)

49
Api/main.py Normal file
View file

@ -0,0 +1,49 @@
# main.py
# Ajuste para garantir que o diretório base do projeto seja incluído no PYTHONPATH
import sys
import os
# Adiciona o diretório atual (onde está o main.py) ao sys.path
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
# Importa a classe principal do FastAPI
from fastapi import FastAPI
from core.system.handlers import register_exception_handlers
# Importa o middleware de CORS
from fastapi.middleware.cors import CORSMiddleware
# Importa as configurações globais da aplicação
from core.configs import settings
# Importa o roteador principal da API versão 1
from api.v1.api import api_router
# Instancia o app FastAPI com um título personalizado
app = FastAPI(title='API Mydocs')
register_exception_handlers(app)
# Adiciona o middleware de CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"], # Domínio do frontend
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Inclui as rotas da versão 1 da API com prefixo definido em settings (ex: /api/v1)
app.include_router(api_router, prefix=settings.API_V1_STR)
# Executa o servidor com Uvicorn se este arquivo for executado diretamente
if __name__ == '__main__':
import uvicorn
uvicorn.run(
"main:app", # Caminho do app para execução
host="0.0.0.0", # Disponibiliza a aplicação externamente
port=8000, # Porta padrão
log_level='info', # Define o nível de log para desenvolvimento
reload=True # Ativa auto-reload durante desenvolvimento
)

39
Api/requirements.txt Normal file
View file

@ -0,0 +1,39 @@
annotated-types==0.7.0
anyio==4.9.0
asyncpg==0.30.0
bcrypt==3.2.0
cffi==1.17.1
click==8.2.1
colorama==0.4.6
cryptography==45.0.2
dnspython==2.7.0
ecdsa==0.19.1
email_validator==2.2.0
fastapi==0.115.12
future==1.0.0
greenlet==3.2.2
h11==0.16.0
idna==3.10
mysql-connector-python==9.3.0
packaging==25.0
passlib==1.7.4
protobuf==5.29.4
pyasn1==0.4.8
pycparser==2.22
pydantic==2.11.4
pydantic-settings==2.9.1
pydantic_core==2.33.2
PyMySQL==1.1.1
python-dateutil==2.9.0.post0
python-dotenv==1.1.0
python-jose==3.4.0
python-multipart==0.0.20
pytz==2025.2
rsa==4.9.1
six==1.17.0
sniffio==1.3.1
SQLAlchemy==2.0.41
starlette==0.46.2
typing-inspection==0.4.0
typing_extensions==4.13.2
uvicorn==0.34.2

View file

@ -0,0 +1,224 @@
{
"info": {
"_postman_id": "385c0e34-a414-4987-9f55-cf650e80f25c",
"name": "Orius",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "41558252"
},
"item": [
{
"name": "Administrativo",
"item": [
{
"name": "Caixa",
"item": [
{
"name": "All",
"protocolProfileBehavior": {
"disableBodyPruning": true
},
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{BearerToken}}",
"type": "string"
}
]
},
"method": "GET",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "username",
"value": "keven@softwiki.com.br",
"type": "text"
},
{
"key": "password",
"value": "123",
"type": "text"
}
]
},
"url": {
"raw": "{{BaseUrlV1}}administrativo/caixa",
"host": [
"{{BaseUrlV1}}administrativo"
],
"path": [
"caixa"
]
}
},
"response": []
},
{
"name": "Create",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{BearerToken}}",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\r\n \"especie_pagamento\": \"D\",\r\n \"caixa_item_id\" : 12,\r\n \"caixa_servico_id\": 9,\r\n \"usuario_servico_id\": 123456,\r\n \"usuario_caixa_id\": null,\r\n \"chave_servico\": null,\r\n \"descricao\": \"1\",\r\n \"data_pagamento\": null,\r\n \"situacao\": \"3\",\r\n \"tipo_documento\": \"C\",\r\n \"tipo_transacao\": \"C\",\r\n \"valor_servico\": 61,\r\n \"valor_pago\": 61,\r\n \"observacao\": null,\r\n \"caixa_cheque_id\": null,\r\n \"hora_pagamento\": null,\r\n \"caixa_id\": null,\r\n \"recibo_id\": null,\r\n \"tipo_servico\": \"17\",\r\n \"qtd\": 1,\r\n \"apresentante\": \"1\",\r\n \"mensalista_id\": null,\r\n \"quitado_caixa_id\": null,\r\n \"registrado\": 1,\r\n \"emolumento\": 33,\r\n \"taxa_judiciaria\": 14,\r\n \"fundesp\": 13,\r\n \"desconto\": 0,\r\n \"valor_documento\": 0,\r\n \"outra_taxa1\": 0,\r\n \"chave_servico_sec\": null,\r\n \"emolumento_item_id\": null,\r\n \"caixa_registroselo_id\": null,\r\n \"fundo_ri\": null,\r\n \"valor_recibo\": null,\r\n \"boleto_pdf\": null,\r\n \"boleto_vencimento\": null,\r\n \"iss\": 1,\r\n \"nlote\": 0,\r\n \"tabela\": \"1\",\r\n \"campo_id\": 5,\r\n \"boleto_id\": null,\r\n \"valor_adicional\": null,\r\n \"pix_id\": null\r\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{BaseUrlV1}}administrativo/caixa",
"host": [
"{{BaseUrlV1}}administrativo"
],
"path": [
"caixa"
]
}
},
"response": []
},
{
"name": "Get",
"protocolProfileBehavior": {
"disableBodyPruning": true
},
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{BearerToken}}",
"type": "string"
}
]
},
"method": "GET",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "username",
"value": "keven@softwiki.com.br",
"type": "text"
},
{
"key": "password",
"value": "123",
"type": "text"
}
]
},
"url": {
"raw": "{{BaseUrlV1}}administrativo/caixa/3",
"host": [
"{{BaseUrlV1}}administrativo"
],
"path": [
"caixa",
"3"
]
}
},
"response": []
},
{
"name": "Delete",
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{BearerToken}}",
"type": "string"
}
]
},
"method": "DELETE",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "username",
"value": "keven@softwiki.com.br",
"type": "text"
},
{
"key": "password",
"value": "123",
"type": "text"
}
]
},
"url": {
"raw": "{{BaseUrlV1}}administrativo/caixa/1",
"host": [
"{{BaseUrlV1}}administrativo"
],
"path": [
"caixa",
"1"
]
}
},
"response": []
}
]
}
]
}
],
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"packages": {},
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"packages": {},
"exec": [
""
]
}
}
],
"variable": [
{
"key": "BaseUrlV1",
"value": "http://127.0.0.1:8000/api/v1/",
"type": "string"
},
{
"key": "BearerToken",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiZXhwIjoxNzUwNjMxNDYxLCJpYXQiOjE3NTAwMjY2NjEsInN1YiI6IjEzIn0.Qnf9kpxgCCUKzjIi3lwR2LZUfpKQeaWhEDUHHZjpBis",
"type": "string"
}
]
}