Projetos /

Luno - Gerador de Escalas de Rotinas

Aplicação desktop (PyQt5) para criação e gestão automatizada de escalas de trabalho semanais para farmácias.

Exemplo de Escala Gerada pelo Luno

O Desafio

A criação manual de escalas de rotinas semanais em farmácias é uma tarefa complexa e demorada. É preciso conciliar as rotinas específicas de cada turno e grupo de trabalho com as folgas individuais dos funcionários, garantindo a cobertura de todas as tarefas e uma distribuição justa.

Fazer isso manualmente, muitas vezes em planilhas, é propenso a erros, difícil de manter e consome um tempo valioso que poderia ser dedicado a outras atividades da farmácia.

A Solução: Aplicação Desktop Luno

Desenvolvi o "Luno", uma aplicação desktop em Python com interface gráfica PyQt5, projetada para simplificar e automatizar a geração de escalas de rotinas em farmácias:

  • Gestão Centralizada: Permite cadastrar funcionários (manualmente ou via importação Excel), definir seus grupos de trabalho (cores) e gerenciar rotinas específicas para cada turno e grupo (salvas em JSON).
  • Gerenciador de Folgas: Interface dedicada para registrar os dias de folga fixos de cada funcionário, salvando os dados em formato JSON para persistência.
  • Dois Modos de Geração:
    • Grupos Fixos (Manual): Aloca funcionários às rotinas baseando-se estritamente no grupo de cor definido, rotacionando dentro do grupo para evitar sobrecarga e respeitando folgas.
    • Rotação Diária (Automático): Distribui aleatoriamente os funcionários disponíveis (sem folga no dia) entre os grupos de rotinas ativos, garantindo cobertura e variação.
  • Visualização Clara da Escala: Apresenta a escala semanal gerada numa tabela (QTableWidget) colorida de acordo com os grupos, facilitando a visualização.
  • Fixação Manual (Pinning): Permite ao usuário clicar com o botão direito numa célula da escala e "fixar" um funcionário específico para aquela rotina/dia, sobrepondo a geração automática.
  • Importação/Exportação Excel: Facilita a entrada de dados (funcionários, rotinas via Excel) e a partilha da escala final gerada (formato Excel).
  • Interface Personalizada: Utiliza um stylesheet customizado (`ui_design.py`) e ícones SVG para um visual moderno e agradável, focado na usabilidade.

Interface da Aplicação Luno

Visualização das principais telas: configuração inicial, gerenciador de rotinas e gerenciador de folgas.

Tela inicial do Luno
Tela principal da aplicação, com painel de controle à esquerda.
Gerenciador de Rotinas
Janela modal para adicionar, editar, remover e importar rotinas por turno e grupo.
Gerenciador de Folgas
Janela modal para definir os dias de folga fixos de cada funcionário.

Código em Destaque

Trecho do `main_app.py` que implementa a lógica de geração da escala no modo "Grupos Fixos". Ele utiliza `deque` para rotacionar os funcionários dentro de cada grupo e verifica as folgas (`days_off_data`) antes de alocar um funcionário a uma célula da escala.

main_app.py (Geração - Grupos Fixos)

# (Trecho adaptado de main_app.py - PharmacyScheduler)
from collections import deque
# ... outras importações ...

class PharmacyScheduler(QMainWindow):
    # ... (init, setup_ui, etc.) ...

    def generate_with_fixed_groups(self):
        # Cria um deque (fila circular) para cada grupo de cor com os funcionários
        group_deques = {group: deque(sorted([name for name, g in self.employees.items() if g == group])) 
                        for group in GROUP_COLORS}
        
        # Itera sobre cada dia da semana (colunas da tabela)
        for day_idx, day in enumerate(DAYS_OF_WEEK):
            col_idx = day_idx + 1 # Índice da coluna na tabela (começa em 1)
            
            # Itera sobre cada rotina (linhas da tabela)
            for row_idx in range(self.schedule_table.rowCount()):
                # self.routine_map mapeia linha da tabela -> grupo de cor
                routine_group = self.routine_map[row_idx] 
                found_employee = None
                
                # Verifica se há funcionários no deque para o grupo da rotina
                if group_deques[routine_group]:
                    # Tenta encontrar um funcionário disponível (sem folga)
                    # Loopa no máximo o número de funcionários no grupo
                    for _ in range(len(group_deques[routine_group])):
                        candidate = group_deques[routine_group][0] # Pega o próximo da fila
                        # Verifica se o candidato NÃO está de folga neste dia
                        # self.days_off_data é um dict: {'NomeFunc': ['SEGUNDA', 'TERÇA'], ...}
                        if day not in self.days_off_data.get(candidate, []):
                            found_employee = candidate # Encontrou!
                            break # Para de procurar neste grupo para esta rotina
                        
                        # Se estava de folga, rotaciona o deque para testar o próximo
                        group_deques[routine_group].rotate(-1) 
                        
                employee_name = found_employee # Nome a ser exibido (pode ser None se ninguém estiver disponível)
                
                # Verifica se há um funcionário fixado (pinned) pelo usuário para esta célula
                if (row_idx, col_idx) in self.pinned_assignments:
                    employee_name = self.pinned_assignments[(row_idx, col_idx)]
                    
                # Se encontrou um funcionário (ou ele estava fixado)
                if employee_name:
                    # Define o texto e a cor da célula na tabela
                    self.set_table_cell(row_idx, col_idx, employee_name, routine_group)
            
            # Após processar todas as rotinas do dia, rotaciona TODOS os deques
            # Isso garante que a próxima pessoa do grupo comece no dia seguinte
            for group in group_deques:
                if group_deques[group]: 
                    group_deques[group].rotate(-1)
    
    # ... (generate_with_daily_rotation, setup_table, set_table_cell, etc.) ...