diff --git a/app.py b/app.py index 8663a15..a773c47 100644 --- a/app.py +++ b/app.py @@ -20,6 +20,7 @@ from routes.service import service_bp from routes.service_roles import service_role_bp from routes.cameras import camera_bp from routes.ambiente import ambiente_bp +from routes.reports import employee_bp import logging from dotenv import load_dotenv from werkzeug.exceptions import BadRequest @@ -142,4 +143,4 @@ if __name__ == "__main__": with app.app_context(): db.create_all() # Cria as tabelas run_all_seeds() - app.run(debug=True, host="0.0.0.0", port=5001) + app.run(debug=True, host="0.0.0.0") diff --git a/docs/reports/create.yml b/docs/reports/create.yml new file mode 100644 index 0000000..70337e6 --- /dev/null +++ b/docs/reports/create.yml @@ -0,0 +1,99 @@ + tags: + - Relatórios de Horários + summary: Criar novo relatório de ponto + description: Registra um novo relatório de horário para um colaborador + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - employee_id + - entry_time + - exit_time + properties: + employee_id: + type: integer + description: ID do colaborador + example: 1 + entry_time: + type: string + description: Horário de entrada + example: "08:05" + exit_time: + type: string + description: Horário de saída + example: "17:10" + date: + type: string + format: date + description: Data do relatório (opcional, padrão é data atual) + example: "2024-03-27" + status: + type: string + description: Status do relatório + enum: ['Regular', 'Atrasado', 'Saída Antecipada'] + example: "Regular" + responses: + '201': + description: Relatório criado com sucesso + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: "Relatório criado com sucesso" + report_id: + type: integer + example: 10 + '400': + description: Requisição inválida + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Dados do relatório inválidos" + '401': + description: Não autorizado + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Token de autenticação inválido" + '404': + description: Colaborador não encontrado + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Colaborador não encontrado" + '500': + description: Erro interno do servidor + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Erro ao processar a solicitação" + components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT \ No newline at end of file diff --git a/docs/reports/delete.yml b/docs/reports/delete.yml new file mode 100644 index 0000000..1ce4faf --- /dev/null +++ b/docs/reports/delete.yml @@ -0,0 +1,60 @@ + tags: + - Relatórios de Horários + summary: Excluir relatório de ponto + description: Remove um relatório de horário específico + security: + - bearerAuth: [] + parameters: + - in: path + name: report_id + required: true + schema: + type: integer + description: ID do relatório a ser excluído + responses: + '200': + description: Relatório excluído com sucesso + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: "Relatório deletado com sucesso" + '401': + description: Não autorizado + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Token de autenticação inválido" + '404': + description: Relatório não encontrado + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Relatório não encontrado" + '500': + description: Erro interno do servidor + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Erro ao processar a solicitação" +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT \ No newline at end of file diff --git a/docs/reports/list.yml b/docs/reports/list.yml new file mode 100644 index 0000000..d662a2b --- /dev/null +++ b/docs/reports/list.yml @@ -0,0 +1,107 @@ + tags: + - Relatórios de Horários + summary: Consulta de relatórios de ponto eletrônico + description: Recupera lista de todos os relatórios de horários dos colaboradores + security: + - bearerAuth: [] + parameters: + - in: query + name: start_date + schema: + type: string + format: date + description: Data inicial para filtro de relatórios + - in: query + name: end_date + schema: + type: string + format: date + description: Data final para filtro de relatórios + - in: query + name: status + schema: + type: array + items: + type: string + enum: ['Regular', 'Atrasado', 'Saída Antecipada'] + description: Filtro por status do relatório + responses: + '200': + description: Lista de relatórios recuperada com sucesso + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: integer + description: ID do relatório + example: 1 + employee_name: + type: string + description: Nome do colaborador + example: "João Silva" + group: + type: string + description: Grupo/Departamento + example: "Drogasil" + contract_type: + type: string + description: Tipo de contrato + example: "Interno" + date: + type: string + format: date + description: Data do relatório + example: "2024-03-27" + entry_time: + type: string + description: Horário de entrada + example: "08:05" + exit_time: + type: string + description: Horário de saída + example: "17:10" + status: + type: string + description: Status do relatório + enum: ['Regular', 'Atrasado', 'Saída Antecipada'] + example: "Atrasado" + '400': + description: Requisição inválida + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Parâmetros de filtro inválidos" + '401': + description: Não autorizado + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Token de autenticação inválido" + '500': + description: Erro interno do servidor + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Erro ao processar a solicitação" +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT \ No newline at end of file diff --git a/docs/reports/update.yml b/docs/reports/update.yml new file mode 100644 index 0000000..53e7df5 --- /dev/null +++ b/docs/reports/update.yml @@ -0,0 +1,90 @@ + tags: + - Relatórios de Horários + summary: Atualizar relatório de ponto + description: Atualiza um relatório de horário existente + security: + - bearerAuth: [] + parameters: + - in: path + name: report_id + required: true + schema: + type: integer + description: ID do relatório a ser atualizado + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + entry_time: + type: string + description: Novo horário de entrada + example: "08:10" + exit_time: + type: string + description: Novo horário de saída + example: "17:15" + status: + type: string + description: Novo status do relatório + enum: ['Regular', 'Atrasado', 'Saída Antecipada'] + example: "Atrasado" + responses: + '200': + description: Relatório atualizado com sucesso + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: "Relatório atualizado com sucesso" + '400': + description: Requisição inválida + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Dados de atualização inválidos" + '401': + description: Não autorizado + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Token de autenticação inválido" + '404': + description: Relatório não encontrado + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Relatório não encontrado" + '500': + description: Erro interno do servidor + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Erro ao processar a solicitação" +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT \ No newline at end of file diff --git a/models/reports.py b/models/reports.py new file mode 100644 index 0000000..b2694aa --- /dev/null +++ b/models/reports.py @@ -0,0 +1,22 @@ +from extensions import db +from sqlalchemy import Column, Integer, String, DateTime, Float, ForeignKey +from sqlalchemy.orm import relationship +from datetime import datetime + +class Employee(db.Model): + __tablename__ = 'employee' + + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + username = db.Column(db.String(80), unique=True, nullable=True) + email = db.Column(db.String(120), unique=True, nullable=False) + group = db.Column(db.String(80), unique=True, nullable=True) + contract_type = db.Column(db.String(80), unique=True, nullable=False) + date = db.Column(db.Date, nullable=True) + expected_entry_time = db.Column(db.DateTime, nullable=False) + expected_exit_time = db.Column(db.DateTime, nullable=False) + + # Relacionamento hierárquico + parent_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) + + def __repr__(self): + return f"" \ No newline at end of file diff --git a/routes/reports.py b/routes/reports.py new file mode 100644 index 0000000..c1d5116 --- /dev/null +++ b/routes/reports.py @@ -0,0 +1,134 @@ +from flask import Blueprint, request, jsonify +from flask_jwt_extended import jwt_required +from flasgger.utils import swag_from +from models.reports import Employee +from models.user import db,User + +employee_bp = Blueprint('employee', __name__, url_prefix='/employee') + +@employee_bp.route('/', methods=['POST']) +@jwt_required() +@swag_from('../docs/reports/create.yml') +def create_employee(): + name = data.get('name') + group = data.get('group') + contract = data.get('contract') + data = request.get_json() + entry = data.get('entry') + exit = data.get('exit') + status = data.get('status') + + # Cria um novo ServiceRole + employee = Employee( + id = 1, + name = name, + group = group, + contract = contract, + data = data, + entry = entry, + exit = exit, + status = status + ) + + # Adiciona e comita no banco de dados + db.session.add(employee) + db.session.commit() + + return jsonify({'message': 'employee criado com sucesso', 'employee_id': employee.id}), 201 + +@employee_bp.route('/', methods=['GET']) +@jwt_required() +@swag_from('../docs/reports/list.yml') +def list_employee(): + page = request.args.get('page', 1, type=int) + per_page = request.args.get('per_page', 10, type=int) + + pagination = Employee.query.paginate(page=page, per_page=per_page, error_out=False) + employee = pagination.items + + return jsonify({ + 'employee': [ + { + 'id': employee.service_id, + 'name': employee.name, + 'group': employee.group, + 'contract': employee.contract, + 'data': employee.data, + 'entry': employee.entry, + 'exit': employee.exit, + 'status': employee.status + + } for employee in employee + ], + 'total': pagination.total, + 'page': pagination.page, + 'per_page': pagination.per_page + }), 200 + +@employee_bp.route('/', methods=['GET']) +@jwt_required() +@swag_from('../docs/reports/get.yml') +def get_employee(employee_id): + employee = Employee.query.get(employee_id) + if not employee: + return jsonify({'error': 'Relatório não encontrada'}), 404 + + return jsonify({ + 'id': employee.service_id, + 'name': employee.name, + 'group': employee.group, + 'contract': employee.contract, + 'data': employee.data, + 'entry': employee.entry, + 'exit': employee.exit, + 'status': employee.status + }), 200 + +@employee_bp.route('/', methods=['PUT']) +@jwt_required() +@swag_from('../docs/reports/update.yml') +def update_employee(employee_id): + name = data.get('name') + group = data.get('group') + contract = data.get('contract') + data = request.get_json() + entry = data.get('entry') + exit = data.get('exit') + status = data.get('status') + + employee = Employee.query.get(employee_id) + if not employee: + return jsonify({'error': 'Relatório não encontrado'}), 404 + + # Atualiza os campos fornecidos + if name: + employee.name = name + if group: + employee.group = group + if contract: + employee.contract = contract + if data: + employee.data = data + if entry: + employee.entry = entry + if exit: + employee.name = exit + if status: + employee.name = status + + db.session.commit() + + return jsonify({'message': 'Relatório atualizado com sucesso', 'employee_id': employee.id}), 200 + +@employee_bp.route('/', methods=['DELETE']) +@jwt_required() +@swag_from('../docs/reports/delete.yml') +def delete_employee(employee_id): + employee = Employee.query.get(employee_id) + if not employee: + return jsonify({'error': 'Relatório não encontrado'}), 404 + + db.session.delete(employee) + db.session.commit() + + return jsonify({'message': 'Relatório deletado com sucesso'}), 200 \ No newline at end of file diff --git a/seeds/reports_seed.py b/seeds/reports_seed.py new file mode 100644 index 0000000..5fa4ef0 --- /dev/null +++ b/seeds/reports_seed.py @@ -0,0 +1,96 @@ +from extensions import db +from models.reports import Employee + +def reports_seed(): + employee = [ + # Usuário 1 + { + "parent_id": 1, + "name":'João Silva', + "group":'Drogasil', + "contract_type":'Interno', + "expected_entry_time":'08:00', + "expected_exit_time":'17:00', + "status":'Ausente' + }, + # Usuário 2 + { + "parent_id": 2, + "name":'João Silva', + "group":'Drogasil', + "contract_type":'Interno', + "expected_entry_time":'08:00', + "expected_exit_time":'17:00', + "status":'Ausente' + }, + + # Usuário 3 + { + "parent_id": 3, + "name":'João Silva', + "group":'Drogasil', + "contract_type":'Interno', + "expected_entry_time":'08:00', + "expected_exit_time":'17:00', + "status":'Ausente' + }, + # Usuário 4 + { + "parent_id": 4, + "name":'João Silva', + "group":'Drogasil', + "contract_type":'Interno', + "expected_entry_time":'08:00', + "expected_exit_time":'17:00', + "status":'Ausente' + }, + + # Usuário 5 + { + "parent_id": 5, + "name":'João Silva', + "group":'Drogasil', + "contract_type":'Interno', + "expected_entry_time":'08:00', + "expected_exit_time":'17:00', + "status":'Ausente' + }, + + # Usuário 6 + { + "parent_id": 6, + "name":'João Silva', + "group":'Drogasil', + "contract_type":'Interno', + "expected_entry_time":'08:00', + "expected_exit_time":'17:00', + "status":'Ausente' + }, + ] + for employee_data in employee: + # Verifica se já existe um papel para o service_id e name fornecidos + existing_employee = db.session.query(Employee).filter_by( + name=employee_data["name"], + group=employee_data["group"], + contract=employee_data["contract"], + data=employee_data["data"], + entry=employee_data["entry"], + exit=employee_data["exit"], + status=employee_data["status"] + ).first() + + # Se não existe, cria o novo papel + if not existing_employee: + employee = Employee( + name=employee_data["name"], + group=employee_data["group"], + contract=employee_data["contract"], + data=employee_data["data"], + entry=employee_data["entry"], + exit=employee_data["exit"], + status=employee_data["status"] + ) + db.session.add(employee) + + # Commit a transação para salvar os dados no banco + db.session.commit() diff --git a/seeds/run_seed.py b/seeds/run_seed.py index 3a36eff..9b8c6ae 100644 --- a/seeds/run_seed.py +++ b/seeds/run_seed.py @@ -12,6 +12,7 @@ from .user_service_roles import seed_user_service_roles from .services_seed import seed_services from .camera_seed import seed_cameras from .ambiente_seed import seed_ambiente +from .reports_seed import reports_seed from extensions import db @@ -31,6 +32,7 @@ def run_all_seeds(): seed_licenses() seed_cameras() seed_ambiente() + reports_seed() print("Seeds executados com sucesso!") except Exception as e: db.session.rollback() diff --git a/seeds/service_roles.py b/seeds/service_roles.py index 8cad372..d32a2a8 100644 --- a/seeds/service_roles.py +++ b/seeds/service_roles.py @@ -58,20 +58,6 @@ def seed_service_roles(): "name": "Admin", "description": "Coordenador de plataforma." }, - - # Disciplinas Role: Coordenador de Disciplinas - #{ - # "service_id": 1, - # "name": "Disciplinas", - # "description": "Coordenador de disciplinas com acesso para atribuir professores às disciplinas e controlar o conteúdo programático de cada matéria." - #}, - - # Professores Role: Professor - #{ - # "service_id": 1, - # "name": "Professores", - # "description": "Professor com acesso para lecionar disciplinas, criar e corrigir provas e acompanhar o desempenho dos alunos nas suas matérias." - #}, ] # Verificar se o role já existe antes de criar