Criação dos endpoint's da tabela client

This commit is contained in:
Kenio 2025-10-17 10:23:31 -03:00
parent 017a06613a
commit 6406f10958
19 changed files with 1086 additions and 0 deletions

View file

@ -0,0 +1,25 @@
from packages.v1.administrativo.schemas.client_schema import ClientIdSchema
from packages.v1.administrativo.repositories.client.client_delete_repository import ClientDeleteRepository
class ClientDeleteAction:
"""
Action para a exclusão de um registro na tabela 'client'.
Utiliza o schema com o ID do cliente e delega a operação ao repositório.
"""
def execute(self, client_schema: ClientIdSchema):
"""
Executa a lógica de exclusão do cliente.
A exclusão requer apenas a chave primária ('client_id'), que deve ser
encapsulada no schema 'ClientIdSchema'.
:param client_schema: Schema contendo o ID do cliente a ser excluído.
:return: Resultado da operação de exclusão do repositório.
"""
# Instancia o repositório específico para a exclusão de clientes
delete_repository = ClientDeleteRepository()
# Chama o método execute do repositório, passando o schema do cliente
return delete_repository.execute(client_schema)

View file

@ -0,0 +1,31 @@
from abstracts.action import BaseAction
# O repositório deve ser adaptado para a listagem (indexação) da tabela 'client'
from packages.v1.administrativo.repositories.client.client_index_repository import ClientIndexRepository
from typing import Tuple, List, Dict, Any
class ClientIndexAction(BaseAction):
"""
Action responsável por orquestrar a listagem (indexação) de todos
os registros da tabela 'client' com suporte a paginação.
"""
# O método execute recebe 'first' e 'skip' para paginação
def execute(self, first: int, skip: int) -> Tuple[List[Dict[str, Any]], int]:
"""
Executa a lógica de listagem de clientes com paginação.
:param first: Número máximo de registros a retornar (LIMIT).
:param skip: Número de registros a pular (OFFSET).
:return: Tupla com a lista de clientes e o total de registros.
"""
# Instânciamento do repositório de indexação (listagem) de clientes
# Supondo que ClientIndexRepository é onde a lógica de acesso ao BD está (SELECT * FROM client LIMIT first OFFSET skip)
client_index_repository = ClientIndexRepository()
# Execução do repositório para buscar os clientes com paginação
# A resposta (response) conteria os campos da DDL: client_id, cns, name, date_register, state, city, responsible, consultant, type_contract
response, total_records = client_index_repository.execute(first, skip)
# Retorno da informação
return response, total_records

View file

@ -0,0 +1,27 @@
from packages.v1.administrativo.schemas.client_schema import ClientSaveSchema
from packages.v1.administrativo.repositories.client.client_save_repository import ClientSaveRepository
class ClientSaveAction:
"""
Action responsável por orquestrar a operação de salvar (inserir ou atualizar)
um registro na tabela 'client'.
"""
def execute(self, client_schema: ClientSaveSchema):
"""
Executa a lógica de salvamento do cliente.
O schema 'ClientSaveSchema' deve conter todos os campos necessários
para a operação de persistência, baseados na DDL:
cns, name, date_register (opcional na entrada, pois tem DEFAULT),
state, city, responsible, consultant, e type_contract.
:param client_schema: Schema contendo os dados do cliente a serem salvos.
:return: Resultado da operação de salvamento do repositório.
"""
# Instancia o repositório específico para a operação de salvar clientes
save_repository = ClientSaveRepository()
# Chama o método execute do repositório, passando o objeto schema
return save_repository.execute(client_schema)

View file

@ -0,0 +1,31 @@
from abstracts.action import BaseAction
from packages.v1.administrativo.schemas.client_schema import ClientSchema
from packages.v1.administrativo.repositories.client.client_show_repository import ClientShowRepository
class ClientShowAction(BaseAction):
"""
Action responsável por orquestrar a visualização (show) de um registro
único na tabela 'client', geralmente utilizando o 'client_id'.
"""
def execute(self, client_schema: ClientSchema):
"""
Executa a lógica de busca e exibição do cliente.
O schema 'ClientSchema' é usado para transportar o 'client_id', que
será o critério principal para buscar os dados completos do cliente:
cns, name, date_register, state, city, responsible, consultant,
e type_contract.
:param client_schema: Schema contendo o ID do cliente a ser exibido.
:return: O registro de cliente encontrado ou None/erro.
"""
# Instânciamento do repositório de visualização (show)
show_repository = ClientShowRepository()
# Execução do repositório
response = show_repository.execute(client_schema)
# Retorno da informação
return response

View file

@ -0,0 +1,28 @@
from packages.v1.administrativo.schemas.client_schema import ClientUpdateSchema
from packages.v1.administrativo.repositories.client.client_update_repository import ClientUpdateRepository
class ClientUpdateAction:
"""
Action responsável por orquestrar a operação de atualização (UPDATE)
de um registro na tabela 'client', identificado pelo seu ID.
"""
def execute(self, client_id: int, client_schema: ClientUpdateSchema):
"""
Executa a lógica de atualização do cliente.
O 'client_id' identifica qual registro será modificado, e o
'client_schema' contém os novos valores para os campos
(cns, name, state, city, responsible, consultant, type_contract).
O campo 'date_register' geralmente é omitido ou atualizado automaticamente.
:param client_id: ID do cliente a ser atualizado.
:param client_schema: Schema contendo os novos dados do cliente.
:return: Resultado da operação de atualização do repositório.
"""
# Instancia o repositório específico para a operação de atualização de clientes
update_repository = ClientUpdateRepository()
# Chama o método execute do repositório, passando o ID e o objeto schema
return update_repository.execute(client_id, client_schema)

View file

@ -0,0 +1,156 @@
from actions.dynamic_import.dynamic_import import DynamicImport
# Adaptando os Schemas para a entidade 'Client'
from packages.v1.administrativo.schemas.client_schema import (
ClientSchema,
ClientAuthenticateSchema,
ClientSaveSchema,
ClientUpdateSchema,
ClientIdSchema,
ClientFileSchema,
ClientCNSchema # Adaptando 'LogClientIdSchema' para um campo relevante de Cliente, como o 'cns'
)
import json # Necessário para carregar o arquivo app.json
import math
# Carrega as configurações de paginação do app.json
with open('config/app.json', 'r') as f:
app_config = json.load(f)
PAGINATION_FIRST = app_config.get('pagination', {}).get('first', 20)
PAGINATION_SKIP = app_config.get('pagination', {}).get('skip', 0)
class ClientController:
"""
Controller responsável por orquestrar as operações (CRUD e outras buscas)
para a tabela 'client'.
"""
def __init__(self):
# Action responsável por carregar as services de acordo com o estado
self.dynamic_import = DynamicImport()
# Define o pacote que deve ser carregado
self.dynamic_import.set_package("administrativo")
# Define a tabela que o pacote pertence
self.dynamic_import.set_table("client")
pass
# Lista todos os clientes com paginação
def index(self, first: int = PAGINATION_FIRST, skip: int = PAGINATION_SKIP):
# Importação da classe desejada
indexService = self.dynamic_import.service("client_index_service", "IndexService")
# Instânciamento da classe service
self.indexService = indexService()
# Lista todos os clientes, recebendo a lista de dados e o total de registros
data, total_records = self.indexService.execute(first, skip)
# Cálculo dos metadados de paginação
total_pages = math.ceil(total_records / first)
current_page = (skip // first) + 1
next_page = None
# Verifica se existe uma próxima página
if current_page < total_pages:
next_page = current_page + 1
# Retorna a lista de clientes e os metadados de paginação
return {
'message': 'Clientes localizados com sucesso',
'data': data,
'pagination': {
'total_records': total_records,
'total_pages': total_pages,
'current_page': current_page,
'next_page': next_page,
'first': first, # Total de registros por página
'skip': skip # Registros pulados
}
}
# Busca um cliente específico pelo cns (Adaptado de logClient)
def getByCns(self, client_schema: ClientCNSchema):
#Importação da classe desejada
client_cns_service = self.dynamic_import.service('client_cns_service', 'ClientCNSService')
# Instânciamento da classe desejada
self.client_cns_service = client_cns_service()
# Busca e retorna o cliente desejado
return {
'message': 'Cliente(s) localizados com sucesso pelo CNS',
'data': self.client_cns_service.execute(client_schema)
}
# Busca um cliente específico pelo ID (client_id)
def show(self, client_schema: ClientSchema):
#Importação da classe desejada
show_service = self.dynamic_import.service('client_show_service', 'ShowService')
# Instânciamento da classe desejada
self.show_service = show_service()
# Busca e retorna o cliente desejado
return {
'message': 'Cliente localizado com sucesso',
'data': self.show_service.execute(client_schema)
}
# Cadastra um novo cliente
def save(self, client_schema: ClientSaveSchema):
#Importação da classe desejada
save_service = self.dynamic_import.service('client_save_service', 'ClientSaveService')
# Instânciamento da classe desejada
self.save_service = save_service()
# Busca e retorna o cliente desejado
return {
'message': 'Cliente salvo com sucesso',
'data': self.save_service.execute(client_schema)
}
# Atualiza os dados de um cliente
def update(self, client_id: int, client_schema: ClientUpdateSchema):
#Importação da classe desejada
update_service = self.dynamic_import.service('client_update_service', 'ClientUpdateService')
# Instânciamento da classe desejada
self.update_service = update_service()
# Busca e retorna o cliente desejado
return {
'message': 'Cliente atualizado com sucesso',
'data': self.update_service.execute(client_id, client_schema)
}
# Exclui um cliente
def delete(self, client_schema: ClientIdSchema):
#Importação da classe desejada
delete_service = self.dynamic_import.service('client_delete_service', 'DeleteService')
# Instânciamento da classe desejada
self.delete_service = delete_service()
# Busca e retorna o cliente desejado
return {
'message': 'Cliente removido com sucesso',
'data': self.delete_service.execute(client_schema)
}
# Métodos específicos do Log que não se aplicam diretamente a Client foram removidos ou adaptados:
# getGed, getServer, getDatabase, getBackup, getDisk, getWarning (Estes parecem ser específicos de logs/monitoramento).
# Mantendo apenas as operações CRUD e buscas por campos relevantes (CNS, State, ID).
pass

View file

@ -0,0 +1,133 @@
# Importação de bibliotecas
from typing import Optional
from fastapi import APIRouter, Body, Depends, status
from actions.jwt.get_current_user import get_current_user
from packages.v1.administrativo.controllers.user_controller import UserController
from packages.v1.administrativo.schemas.user_schema import (
UserSchema,
UserAuthenticateSchema,
UserSaveSchema,
UserUpdateSchema,
UserEmailSchema,
UserIdSchema
)
# Inicializa o roteador para as rotas de usuário
router = APIRouter()
# Instânciamento do controller desejado
user_controller = UserController()
# Autenticação de usuário
@router.post('/authenticate',
status_code=status.HTTP_200_OK,
summary='Cria o token de acesso do usuário',
response_description='Retorna o token de acesso do usuário')
async def index(user_authenticate_schema : UserAuthenticateSchema):
# Efetua a autenticação de um usuário junto ao sistema
response = user_controller.authenticate(user_authenticate_schema)
# Retorna os dados localizados
return response
# Dados do usuário logado
@router.get('/me',
status_code=status.HTTP_200_OK,
summary='Retorna os dados do usuário que efetuou o login',
response_description='Dados do usuário que efetuou o login' )
async def me(current_user: dict = Depends(get_current_user)):
# Busca os dados do usuário logado
response = user_controller.me(current_user)
# Retorna os dados localizados
return response
# Lista todos os usuários
@router.get('/',
status_code=status.HTTP_200_OK,
summary='Lista todos os usuário cadastrados',
response_description='Lista todos os usuário cadastrados')
async def index(current_user: dict = Depends(get_current_user)):
# Busca todos os usuários cadastrados
response = user_controller.index()
# Retorna os dados localizados
return response
# Localiza um usuário pelo email
@router.get('/email',
status_code=status.HTTP_200_OK,
summary='Busca um registro em especifico por e-mail informado',
response_description='Busca um registro em especifico')
async def getEmail(email : str, current_user: dict = Depends(get_current_user)):
# Cria o schema com os dados recebidos
usuario_schema = UserEmailSchema(email=email)
# Busca um usuário especifico pelo e-mail
response = user_controller.getEmail(usuario_schema)
# Retorna os dados localizados
return response
# Localiza um usuário pelo ID
@router.get('/{user_id}',
status_code=status.HTTP_200_OK,
summary='Busca um registro em especifico pelo ID do usuário',
response_description='Busca um registro em especifico')
async def show(user_id : int, current_user: dict = Depends(get_current_user)):
# Cria o schema com os dados recebidos
usuario_schema = UserIdSchema(user_id=user_id)
# Busca um usuário especifico pelo ID
response = user_controller.show(usuario_schema)
# Retorna os dados localizados
return response
# Cadastro de usuários
@router.post('/',
status_code=status.HTTP_200_OK,
summary='Cadastra um usuário',
response_description='Cadastra um usuário')
async def save(usuario_schema : UserSaveSchema, current_user: dict = Depends(get_current_user)):
# Efetua o cadastro do usuário junto ao banco de dados
response = user_controller.save(usuario_schema)
# Retorna os dados localizados
return response
# Atualiza os dados de usuário
@router.put('/{user_id}',
status_code=status.HTTP_200_OK,
summary='Atualiza um usuário',
response_description='Atualiza um usuário')
async def update(user_id : int, usuario_schema : UserUpdateSchema, current_user: dict = Depends(get_current_user)):
# Efetua a atualização dos dados de usuário
response = user_controller.update(user_id, usuario_schema)
# Retorna os dados localizados
return response
# Exclui um determinado usuário
@router.delete('/{user_id}',
status_code=status.HTTP_200_OK,
summary='Remove um usuário',
response_description='Remove um usuário')
async def delete(user_id : int, current_user: dict = Depends(get_current_user)):
# Cria o schema com os dados recebidos
usuario_schema = UserIdSchema(user_id=user_id)
# Efetua a exclusão de um determinado usuário
response = user_controller.delete(usuario_schema)
# Retorna os dados localizados
return response

View file

@ -0,0 +1,38 @@
from packages.v1.administrativo.schemas.client_schema import \
ClientIdSchema
from abstracts.repository import BaseRepository
from fastapi import HTTPException, status
class ClientDeleteRepository(BaseRepository):
"""
Repositório responsável pela operação de exclusão (DELETE) de um
registro na tabela 'client', usando o client_id como critério.
"""
def execute(self, client_schema: ClientIdSchema):
try:
# Montagem do sql para exclusão
sql = """ DELETE FROM client c WHERE c.client_id = :clientId """
# Preenchimento de parâmetros
params = {
"clientId" : client_schema.client_id
}
# Execução do sql
response = self.run(sql, params)
# Retorna o resultado (número de linhas afetadas)
return response
except Exception as e:
# Informa que houve uma falha na exclusão do cliente
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"Erro ao excluir cliente: {e}"
)

View file

@ -0,0 +1,51 @@
from abstracts.repository import BaseRepository
from typing import Tuple, List, Dict, Any
class ClientIndexRepository(BaseRepository):
"""
Repositório responsável por buscar e retornar todos os registros
da tabela 'client' (indexação), com suporte a paginação.
"""
# O método execute recebe 'first' (limite) e 'skip' (offset)
def execute(self, first: int, skip: int) -> Tuple[List[Dict[str, Any]], int]:
"""
Executa a busca de clientes com paginação e retorna o total de registros.
O SELECT retorna todos os campos da DDL da tabela client:
client_id, cns, name, date_register, state, city, responsible, consultant, type_contract.
:param first: Número máximo de registros a retornar (LIMIT).
:param skip: Número de registros a pular (OFFSET).
:return: Uma tupla contendo a lista de clientes e o total de registros.
"""
# 1. SQL para contar o total de registros (ignorando LIMIT/OFFSET)
sql_count = """ SELECT COUNT(*)
FROM client c """
# Assumindo que self.fetch_one() retorna o resultado do banco
total_records = self.fetch_one(sql_count)['COUNT(*)']
# 2. SQL para listar os clientes com LIMIT e OFFSET (Paginação)
# Selecionando todos os campos da DDL
sql = f""" SELECT c.client_id,
c.cns,
c.name,
c.date_register,
c.state,
c.city,
c.responsible,
c.consultant,
c.type_contract
FROM client c
LIMIT {first} OFFSET {skip} """
# Execução do sql para buscar múltiplos registros
# Assumindo que self.fetch_all() retorna a lista de dicionários
response = self.fetch_all(sql)
# Retorna os dados localizados e o total de registros
return response, total_records

View file

@ -0,0 +1,74 @@
from fastapi import HTTPException, status
from abstracts.repository import BaseRepository
from packages.v1.administrativo.schemas.client_schema import ClientSaveSchema # Importação do schema ClientSaveSchema
class ClientSaveRepository(BaseRepository):
"""
Repositório responsável pela operação de salvar/atualizar (Upsert)
um registro na tabela 'client', utilizando a lógica INSERT...ON DUPLICATE KEY UPDATE.
"""
def execute(self, client_schema: ClientSaveSchema):
try:
# SQL adaptado para MySQL: INSERT ... ON DUPLICATE KEY UPDATE para a tabela 'client'.
# Colunas para o INSERT (todos os campos da DDL):
insert_cols = """
client_id, cns, name, date_register, state, city, responsible, consultant, type_contract
"""
# Valores para o INSERT (usando os placeholders):
insert_vals = """
:client_id, :cns, :name, :date_register, :state, :city, :responsible, :consultant, :type_contract
"""
# Ações no ON DUPLICATE KEY UPDATE (atualiza os campos, e o date_register como data de atualização)
update_actions = """
cns = VALUES(cns),
name = VALUES(name),
date_register = NOW(), # Atualiza o timestamp para o momento da modificação
state = VALUES(state),
city = VALUES(city),
responsible = VALUES(responsible),
consultant = VALUES(consultant),
type_contract = VALUES(type_contract)
"""
sql = f"""
INSERT INTO client ({insert_cols})
VALUES ({insert_vals})
ON DUPLICATE KEY UPDATE
{update_actions};
"""
# Preenchimento de parâmetros. client_id será None/0 para INSERT ou o ID para UPDATE.
params = {
'client_id': client_schema.client_id,
'cns': client_schema.cns,
'name': client_schema.name,
'date_register': client_schema.date_register,
'state': client_schema.state,
'city': client_schema.city,
'responsible': client_schema.responsible,
'consultant': client_schema.consultant,
'type_contract': client_schema.type_contract,
}
# Execução do SQL.
result = self.run_and_return(sql, params)
# Se for um INSERT e a execução retornar um ID, retorna o novo ID do cliente.
if not client_schema.client_id and result:
return result
# Se for um UPDATE ou um resultado de sucesso da operação.
return result
except Exception as e:
# Informa que houve uma falha ao salvar/atualizar o cliente
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"Erro ao salvar cliente: {e}"
)

View file

@ -0,0 +1,60 @@
from abstracts.repository import BaseRepository
from packages.v1.administrativo.schemas.client_schema import ClientSchema
from fastapi import HTTPException, status
class ClientShowRepository(BaseRepository):
"""
Repositório responsável por buscar um registro único na tabela 'client'
utilizando a chave primária 'client_id'.
"""
def execute(self, client_schema: ClientSchema):
"""
Executa a busca de um cliente pelo seu ID.
:param client_schema: Schema contendo o client_id.
:return: O registro de cliente encontrado ou None.
"""
try:
# Montagem do sql. O SELECT retorna todos os campos da DDL:
# client_id, cns, name, date_register, state, city, responsible, consultant, type_contract.
sql = """ SELECT c.client_id,
c.cns,
c.name,
c.date_register,
c.state,
c.city,
c.responsible,
c.consultant,
c.type_contract
FROM client c
WHERE c.client_id = :clientId """
# Preenchimento de parâmetros
params = {
'clientId' : client_schema.client_id
}
# Execução do sql para buscar um único registro
response = self.fetch_one(sql, params)
# Se não encontrar o cliente, pode-se lançar uma exceção
if not response:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Cliente com ID {client_schema.client_id} não encontrado."
)
return response
except HTTPException as e:
raise e
except Exception as e:
# Informa que houve uma falha na busca do cliente
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"Erro ao buscar cliente: {e}"
)

View file

@ -0,0 +1,87 @@
from abstracts.repository import BaseRepository
from packages.v1.administrativo.schemas.client_schema import ClientUpdateSchema
from fastapi import HTTPException, status
# A importação de 'datetime' foi removida no código original e não é necessária aqui.
class ClientUpdateRepository(BaseRepository):
"""
Repositório responsável pela atualização (UPDATE) dinâmica de um
registro na tabela 'client', identificado pelo 'client_id'.
"""
def execute(self, client_id: int, client_schema: ClientUpdateSchema):
try:
updates = []
params = {}
# --- Mapeamento e inclusão dos campos da DDL para atualização dinâmica ---
# cns
if client_schema.cns is not None:
updates.append("cns = :cns")
params["cns"] = client_schema.cns
# name
if client_schema.name is not None:
updates.append("name = :name")
params["name"] = client_schema.name
# state
if client_schema.state is not None:
updates.append("state = :state")
params["state"] = client_schema.state
# city
if client_schema.city is not None:
updates.append("city = :city")
params["city"] = client_schema.city
# responsible
if client_schema.responsible is not None:
updates.append("responsible = :responsible")
params["responsible"] = client_schema.responsible
# consultant
if client_schema.consultant is not None:
updates.append("consultant = :consultant")
params["consultant"] = client_schema.consultant
# type_contract
if client_schema.type_contract is not None:
updates.append("type_contract = :type_contract")
params["type_contract"] = client_schema.type_contract
# Os campos 'client_id' (chave) e 'date_register' (timestamp de criação) não são atualizados dinamicamente.
if not updates:
# Se não houver campos para atualizar, retorna False
return False
params["client_id"] = client_id
# SQL para UPDATE
sql = f"UPDATE client SET {', '.join(updates)} WHERE client_id = :client_id;"
# Executa a query
result = self.run(sql, params)
if result is None or result == 0:
# Se 0 linhas afetadas, pode ser que o cliente não exista ou não houve alteração.
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail='Nenhum cliente localizado para esta solicitação ou nenhuma alteração realizada.'
)
# Retorna True, indicando o sucesso da operação
return True
except Exception as e:
# Informa que houve uma falha na atualização do cliente
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"Erro ao atualizar cliente: {e}"
)

View file

@ -0,0 +1,113 @@
from pydantic import BaseModel, constr, field_validator, model_validator
from fastapi import HTTPException, status
from typing import Optional
from datetime import datetime
# Funções utilitárias para sanitização de entradas (evitar XSS, SQLi etc.)
# É necessário importar a função de sanitização se for utilizada nos validadores
from actions.validations.text import Text
# ----------------------------------------------------
# Schema base - Representa a estrutura completa do Cliente (usado em Show e Index)
# ----------------------------------------------------
class ClientSchema(BaseModel):
# Campos da DDL, todos opcionais para o Schema base (principalmente para leitura)
client_id: Optional[int] = None
cns: Optional[str] = None
name: Optional[str] = None
date_register: Optional[datetime] = None
state: Optional[str] = None
city: Optional[str] = None
responsible: Optional[str] = None
consultant: Optional[str] = None
type_contract: Optional[str] = None
class Config:
# Permite que o Pydantic mapeie campos vindos do banco (ex: via ORM)
from_attributes = True
# ----------------------------------------------------
# Schema para operações que requerem apenas o ID
# ----------------------------------------------------
class ClientIdSchema(BaseModel):
client_id: Optional[int] = None
# Valida se o ID não está vazio
@model_validator(mode='after')
def validate_client_id(self):
if not self.client_id or self.client_id <= 0:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail='O ID do cliente é obrigatório para esta operação.'
)
return self
# ----------------------------------------------------
# Schema para localizar cliente pelo CNS
# ----------------------------------------------------
class ClientCNSSchema(BaseModel):
cns: Optional[str] = None
# Sanitiza o input
@field_validator('cns')
def sanitize_cns(cls, v):
if v:
return Text.sanitize_input(v) # Mantendo o padrão de sanitização
return v
# Valida se o campo não está vazio
@model_validator(mode='after')
def validate_cns(self):
if not self.cns or len(self.cns.strip()) == 0:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail='Informe um CNS para a busca.'
)
return self
# ----------------------------------------------------
# Schema para cadastrar (SAVE) um novo cliente (name é NOT NULL)
# ----------------------------------------------------
class ClientSaveSchema(BaseModel):
# Opcional, pois é AUTO_INCREMENT, mas pode ser usado para Upsert
client_id: Optional[int] = None
# name é NOT NULL na DDL, então é obrigatório no save
name: constr(min_length=1, max_length=550)
# Os demais são NULL DEFAULT ou com DEFAULT, então podem ser Optional na entrada
cns: Optional[str] = None
state: Optional[str] = None
city: Optional[str] = None
responsible: Optional[str] = None
consultant: Optional[str] = None
type_contract: Optional[str] = None
# Sanitiza os inputs de string
@field_validator('cns', 'name', 'state', 'city', 'responsible', 'consultant', 'type_contract')
def validate_and_sanitize_fields(cls, v):
if v is not None:
# Assumindo que Text.sanitize_input existe e faz a sanitização
return Text.sanitize_input(v)
return v
# ----------------------------------------------------
# Schema para atualizar (UPDATE) um cliente (tudo opcional)
# ----------------------------------------------------
class ClientUpdateSchema(BaseModel):
# Todos os campos que podem ser atualizados são opcionais
cns: Optional[str] = None
name: Optional[constr(min_length=1, max_length=550)] = None
state: Optional[str] = None
city: Optional[str] = None
responsible: Optional[str] = None
consultant: Optional[str] = None
type_contract: Optional[str] = None
# Sanitiza os inputs de string
@field_validator('cns', 'name', 'state', 'city', 'responsible', 'consultant', 'type_contract')
def validate_and_sanitize_fields(cls, v):
if v is not None:
# Assumindo que Text.sanitize_input existe e faz a sanitização
return Text.sanitize_input(v)
return v

View file

@ -0,0 +1,26 @@
from packages.v1.administrativo.schemas.client_schema import ClientIdSchema
from packages.v1.administrativo.actions.client.client_delete_action import ClientDeleteAction
class ClientDeleteService:
"""
Service responsável por orquestrar a exclusão de um cliente,
delegando a execução para a Action correspondente.
"""
def execute(self, client_schema: ClientIdSchema):
"""
Executa o serviço de exclusão de um cliente.
:param client_schema: Schema contendo o client_id do registro a ser excluído.
:return: Resultado da operação de exclusão (geralmente o número de linhas afetadas).
"""
# Instânciamento de ação
delete_action = ClientDeleteAction()
# Executa a ação em questão
data = delete_action.execute(client_schema)
# Retorno da informação
return data

View file

@ -0,0 +1,36 @@
from fastapi import HTTPException, status
from packages.v1.administrativo.schemas.client_schema import ClientSchema
from packages.v1.administrativo.actions.client.client_index_action import ClientIndexAction
class ClientIndexService:
"""
Service responsável por orquestrar a listagem (indexação) de todos
os clientes, delegando a busca para a Action correspondente.
"""
# O método execute pode ser adaptado para receber 'first' e 'skip' se a Action/Repository suportar paginação.
# No entanto, mantendo o padrão da assinatura do arquivo de referência, ele não recebe parâmetros aqui.
def execute(self):
"""
Executa o serviço de listagem de clientes.
:return: Lista de registros de clientes.
"""
# Instânciamento de ação
index_action = ClientIndexAction()
# Executa a busca de todos os clientes (a Action/Repository fará a busca, potencialmente com paginação)
data = index_action.execute()
# Verifica se foram localizados registros
if not data:
# Retorna uma exceção
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Não foi possível localizar os clientes'
)
# Retorna as informações localizadas
return data

View file

@ -0,0 +1,64 @@
from actions.dynamic_import.dynamic_import import DynamicImport
from packages.v1.administrativo.schemas.client_schema import ClientSaveSchema, ClientCNSSchema
from packages.v1.administrativo.actions.client.client_save_action import ClientSaveAction
from fastapi import status, HTTPException
class ClientSaveService:
"""
Service responsável por orquestrar o salvamento (INSERT/UPDATE) de um cliente.
Inclui validações de regra de negócio, como a unicidade do CNS.
"""
def __init__(self):
# Action responsável por carregar as services de acordo com o estado
self.dynamic_import = DynamicImport()
# Define o pacote que deve ser carregado
self.dynamic_import.set_package("administrativo")
# Define a tabela que o pacote pertence
self.dynamic_import.set_table("client")
pass
def execute(self, client_schema: ClientSaveSchema):
"""
Executa a lógica de serviço para salvar um cliente.
:param client_schema: Schema contendo os dados do cliente a serem salvos.
:return: Resultado da operação de salvamento.
"""
# Armazena possíveis erros de validação
errors = []
# --- Validação de Unicidade do CNS (Cadastro Nacional de Saúde) ---
if client_schema.cns:
# Importação de service de busca por CNS (Adaptado de user_get_email_service)
cns_service = self.dynamic_import.service("client_get_cns_service", "GetCNSService")
# Instânciamento da service
self.cns_service = cns_service()
# Tenta localizar o cliente pelo CNS, sem levantar erro HTTP se não encontrar (o False no segundo param)
self.response = self.cns_service.execute(ClientCNSSchema(cns=client_schema.cns), False)
# Se houver retorno, significa que o CNS já está sendo utilizado
if self.response:
# Se for um UPDATE (client_id existe) e o CNS encontrado pertencer ao próprio cliente que está atualizando,
# não deve ser considerado erro. A validação precisa garantir que o CNS pertence a OUTRO cliente.
if not client_schema.client_id or self.response.get('client_id') != client_schema.client_id:
errors.append({'input': 'cns', 'message': 'O CNS informado já está sendo utilizado por outro cliente.'})
# Se houver erros de validação, informa
if errors:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=errors
)
# Instânciamento de ações
save_action = ClientSaveAction()
# Executa a ação de persistência
return save_action.execute(client_schema)

View file

@ -0,0 +1,34 @@
from fastapi import HTTPException, status
from packages.v1.administrativo.schemas.client_schema import ClientSchema
from packages.v1.administrativo.actions.client.client_show_action import ClientShowAction
class ClientShowService:
"""
Service responsável por orquestrar a visualização de um cliente específico
(geralmente pelo client_id), delegando a execução para a Action.
"""
def execute(self, client_schema: ClientSchema):
"""
Executa o serviço de visualização de um cliente.
:param client_schema: Schema contendo o client_id.
:return: O registro do cliente encontrado.
"""
# Instânciamento de ação
show_action = ClientShowAction()
# Executa a ação em questão
data = show_action.execute(client_schema)
if not data:
# Retorna uma exceção se o registro não for encontrado
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Não foi possível localizar o registro do cliente'
)
# Retorno da informação
return data

View file

@ -0,0 +1,65 @@
from packages.v1.administrativo.schemas.client_schema import ClientUpdateSchema, ClientCNSSchema
from packages.v1.administrativo.actions.client.client_update_action import ClientUpdateAction
from actions.dynamic_import.dynamic_import import DynamicImport
from fastapi import status, HTTPException
class ClientUpdateService:
"""
Service responsável por orquestrar a atualização de um cliente,
incluindo validações de regra de negócio antes de delegar a ação.
"""
def __init__(self):
# Action responsável por carregar as services de acordo com o estado
self.dynamic_import = DynamicImport()
# Define o pacote que deve ser carregado
self.dynamic_import.set_package("administrativo")
# Define a tabela que o pacote pertence
self.dynamic_import.set_table("client")
pass
def execute(self, client_id: int, client_schema: ClientUpdateSchema):
"""
Executa o serviço de atualização de um cliente.
:param client_id: ID do cliente a ser atualizado.
:param client_schema: Schema contendo os dados do cliente para atualização.
:return: Resultado da operação de atualização.
"""
# Armazena possíveis erros de validação
errors = []
# --- Validação de Unicidade do CNS (Cadastro Nacional de Saúde) ---
if client_schema.cns:
# Importação de service de busca por CNS
cns_service = self.dynamic_import.service("client_get_cns_service", "GetCNSService")
# Instânciamento da service
self.cns_service = cns_service()
# Tenta localizar o cliente pelo CNS, sem levantar erro HTTP se não encontrar (o False no segundo param)
# É necessário usar um schema de busca específico para o CNS
self.response = self.cns_service.execute(ClientCNSSchema(cns=client_schema.cns), False)
# Se houver retorno, significa que o CNS já está sendo utilizado
if self.response:
# O CNS é um erro APENAS se pertencer a outro cliente, e não ao que está sendo atualizado.
if self.response.get('client_id') != client_id:
errors.append({'input': 'cns', 'message': 'O CNS informado já está sendo utilizado por outro cliente.'})
# Se houver erros de validação, informa
if errors:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=errors
)
# Instânciamento da Action de atualização
update_action = ClientUpdateAction()
# Executa a ação de atualização
return update_action.execute(client_id, client_schema)

View file

@ -4,6 +4,7 @@ from fastapi import APIRouter
# Importa os módulos de rotas específicos # Importa os módulos de rotas específicos
from packages.v1.administrativo.endpoints import user_endpoint from packages.v1.administrativo.endpoints import user_endpoint
from packages.v1.administrativo.endpoints import log_endpoint from packages.v1.administrativo.endpoints import log_endpoint
from packages.v1.administrativo.endpoints import client_endpoint
# Cria uma instância do APIRouter que vai agregar todas as rotas da API # Cria uma instância do APIRouter que vai agregar todas as rotas da API
api_router = APIRouter() api_router = APIRouter()
@ -18,3 +19,9 @@ api_router.include_router(
log_endpoint.router, prefix="/administrativo/log", tags=["Gerenciamento de log's"] log_endpoint.router, prefix="/administrativo/log", tags=["Gerenciamento de log's"]
) )
# Inclui as rotas de client
api_router.include_router(
client_endpoint.router, prefix="/administrativo/client", tags=["Gerenciamento de client's"]
)