from fastapi import APIRouter, Depends, HTTPException, status from sqlmodel import Session, select import uuid import datetime from app.core.database import get_session from app.api.deps import get_current_user from app.models.user import User from app.models.procedure import Procedure from app.models.procedure_type import ProcedureType from app.models.team_procedure_value import TeamProcedureValue from app.models.membership import Membership from app.schemas.procedure import ProcedureCreate, ProcedureRead, ProcedureUpdate router = APIRouter(prefix="/procedures", tags=["Procedures"]) @router.post("/", response_model=ProcedureRead, status_code=status.HTTP_201_CREATED) def create_procedure( procedure_in: ProcedureCreate, session: Session = Depends(get_session), current_user: User = Depends(get_current_user) ): """ Cria um novo procedimento ("A Rota de Escrita"). Aplica as regras de negócio: - Valida o médico a partir do token (via `get_current_user`). - Identifica o time associado ao médico (caso não pertença a nenhum, retorna erro 400). - Congela o valor: busca na tabela de valores de procedimentos do time qual o preço do procedimento neste exato momento e salva no campo `value_at_time`. """ # 1. Identificar o Time: O backend busca a qual team_id esse médico pertence membership = session.exec(select(Membership).where(Membership.user_id == current_user.id)).first() if not membership: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="O médico não pertence a nenhum time." ) # 2. Congelar o Valor: O código busca na tabela team_procedure_values qual é o preço daquele procedimento para aquele time específico team_procedure_value = session.exec( select(TeamProcedureValue) .where(TeamProcedureValue.team_id == membership.team_id) .where(TeamProcedureValue.procedure_type_id == procedure_in.procedure_type_id) ).first() # Define o valor do procedimento (usa o custom_value, se não existir mapeamento usa 0 ou dispara erro) # Por segurança, caso não esteja mapeado, vamos preencher com 0 e o admin depois ajusta, # Ou podemos rejeitar a criação se não tiver preço. Adotaremos 0 como fallback coerente. value_at_time = team_procedure_value.custom_value if team_procedure_value else 0 # 3. Criar a Procedure associando todos os dados # Se o cliente enviou um doctor_id (via form), usamos ele. Caso contrário, usamos o logado. final_doctor_id = procedure_in.doctor_id or current_user.id procedure = Procedure( **procedure_in.model_dump(exclude={"doctor_id"}), doctor_id=final_doctor_id, team_id=membership.team_id, value_at_time=value_at_time ) session.add(procedure) session.commit() session.refresh(procedure) return procedure @router.patch("/{procedure_id}", response_model=ProcedureRead) def update_procedure( *, session: Session = Depends(get_session), procedure_id: uuid.UUID, procedure_in: ProcedureUpdate, current_user: User = Depends(get_current_user) ): """ Atualiza um procedimento existente. Verifica permissões de equipe e atualiza o valor financeiro se o tipo for alterado. """ # 1. Buscar o procedimento no banco db_proc = session.get(Procedure, procedure_id) if not db_proc: raise HTTPException(status_code=404, detail="Procedimento não encontrado") # 2. Verificar se o usuário pertence ao time do procedimento (Segurança) membership = session.exec( select(Membership).where( Membership.user_id == current_user.id, Membership.team_id == db_proc.team_id ) ).first() if not membership: raise HTTPException( status_code=403, detail="Você não tem permissão para editar procedimentos deste time." ) # 3. Preparar os dados para atualização update_data = procedure_in.model_dump(exclude_unset=True) # 4. Lógica Especial: Se o tipo de procedimento mudou, precisamos atualizar o valor financeiro if "procedure_type_id" in update_data: new_type_id = update_data["procedure_type_id"] # Busca o valor customizado para o time ou o valor base do catálogo custom_val_stmt = select(TeamProcedureValue.custom_value).where( TeamProcedureValue.team_id == db_proc.team_id, TeamProcedureValue.procedure_type_id == new_type_id ) custom_value = session.exec(custom_val_stmt).first() if custom_value is not None: db_proc.value_at_time = custom_value else: # Se não houver valor customizado, busca o valor base do catálogo proc_type = session.get(ProcedureType, new_type_id) if proc_type: db_proc.value_at_time = proc_type.base_value # 5. Aplicar os demais campos dinamicamente for key, value in update_data.items(): setattr(db_proc, key, value) # Definir momento da atualização db_proc.updated_at = datetime.datetime.now(datetime.timezone.utc) # 6. Salvar e retornar session.add(db_proc) session.commit() session.refresh(db_proc) return db_proc @router.delete("/{procedure_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_procedure( *, session: Session = Depends(get_session), procedure_id: uuid.UUID, current_user: User = Depends(get_current_user) ): """ Exclui um procedimento permanentemente. Apenas Administradores do time ou Staff podem realizar esta ação. """ # 1. Buscar o procedimento no banco db_proc = session.get(Procedure, procedure_id) if not db_proc: raise HTTPException(status_code=404, detail="Procedimento não encontrado") # 2. Verificar se o usuário pertence ao time membership = session.exec( select(Membership).where( Membership.user_id == current_user.id, Membership.team_id == db_proc.team_id ) ).first() if not membership: raise HTTPException( status_code=403, detail="Você não tem permissão para acessar este procedimento." ) # NAO USADA PARA O MVP # 3. Regra de Negócio (RBAC): Apenas Admin ou Staff exclui # Nota: membership.role define se ele é 'admin' ou 'member' no time. # current_user.role define se ele é 'staff' no sistema global. # if membership.role != "admin" and current_user.role != RoleEnum.staff: # raise HTTPException( # status_code=403, # detail="Apenas o administrador da equipe ou staff podem excluir registros." # ) # 4. Excluir e salvar (Commit) session.delete(db_proc) session.commit() return {"ok": True}