Componentes: Nicolas J. Alvarenga, Pedro H. P. Neves e Thiago L. Koaski.
Objetivos: Consiste em um sistema de estoque para uma pastelaria, ela tem como objetivo facilitar a gestão do estoque, a comunicação entre a cozinha e o balcão e principalmente a modernização e facilitação do processo da pastelaria
Metodologia de desenvolvimento: A metodologia de desenvolvimento utilizada foi a metodologia do modelo cascata, nós primeiro realizamos o esboço da nossa ideia, em seguida o design do site, em seguida a codificação e por ultimo realizamos testes simples e fomos resolvendo os erros e problemas no código.
import sqlite3
import time
import customtkinter as ctk
from abc import ABC, abstractmethod
# ================================
# [ABSTRAÇÃO] CLASSE ABSTRATA
# ================================
class ItemEstoque(ABC):
"""
Define o contrato para qualquer item que vá para o estoque.
Não pode ser instanciada diretamente.
"""
def __init__(self, nome, quantidade):
# [ENCAPSULAMENTO] Atributos protegidos (começam com _)
self._nome = nome
self._quantidade = quantidade
# [ENCAPSULAMENTO] Getters e Setters para proteção e validação
@property
def nome(self):
return self._nome
@nome.setter
def nome(self, valor):
if not valor.strip():
raise ValueError("Nome não pode ser vazio")
self._nome = valor.strip()
@property
def quantidade(self):
return self._quantidade
@quantidade.setter
def quantidade(self, valor):
if valor < 0:
raise ValueError("Quantidade não pode ser negativa")
self._quantidade = valor
# [POLIMORFISMO] Método abstrato que força as subclasses a implementarem sua própria lógica
@abstractmethod
def atualizar_quantidade(self, qtd):
pass
def __repr__(self):
return f"{self.nome} - {self.quantidade}"
# ================================
# [HERANÇA] PRODUTO HERDA DE ITEMESTOQUE
# ================================
class Produto(ItemEstoque):
def __init__(self, nome, quantidade):
# Chama o construtor da classe pai (ItemEstoque)
super().__init__(nome, quantidade)
# [POLIMORFISMO] Implementação concreta do método abstrato
def atualizar_quantidade(self, qtd):
self.quantidade += qtd # Usa o setter que valida se é negativo
# Métodos específicos desta classe
def adicionar(self, qtd):
self.atualizar_quantidade(qtd)
def remover(self, qtd):
self.atualizar_quantidade(-qtd)
self.quantidade = max(0, self.quantidade)
# ================================
# CLASSE ESTOQUE (PERSISTÊNCIA)
# ================================
class Estoque:
def __init__(self):
self._conn = sqlite3.connect("estoque.db")
self._criar_tabela()
def _criar_tabela(self):
cursor = self._conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS produtos (
nome TEXT PRIMARY KEY,
quantidade INTEGER
)
""")
self._conn.commit()
def adicionar_produto(self, produto: Produto):
# Recebe um OBJETO do tipo Produto
cursor = self._conn.cursor()
cursor.execute(
"INSERT OR REPLACE INTO produtos (nome, quantidade) VALUES (?, ?)",
(produto.nome, produto.quantidade)
)
self._conn.commit()
def excluir_produto(self, nome):
cursor = self._conn.cursor()
cursor.execute("DELETE FROM produtos WHERE nome=?", (nome,))
self._conn.commit()
def listar(self):
cursor = self._conn.cursor()
cursor.execute("SELECT nome, quantidade FROM produtos")
rows = cursor.fetchall()
# Retorna uma lista de OBJETOS Produto
return [Produto(nome, qtd) for nome, qtd in rows]
# ================================
# [ABSTRAÇÃO] CLASSE PEDIDO
# ================================
class Pedido(ABC):
TEMPO_BASE = 600 # 10 minutos (600 segundos)
def __init__(self, mesa, pedido_texto, hora_criacao=None):
self.mesa = mesa
self.pedido_texto = pedido_texto
self.hora_criacao = hora_criacao if hora_criacao else time.time()
self._estado = "Pendente"
@property
def estado(self):
return self._estado
def get_tempo_decorrido(self):
return time.time() - self.hora_criacao
# [POLIMORFISMO] As subclasses definirão como o tempo é calculado/exibido
@abstractmethod
def tempo_restante_str(self):
pass
def acabou_tempo(self):
tempo_decorrido = self.get_tempo_decorrido()
return (self.TEMPO_BASE - tempo_decorrido) <= 0
# ================================
# [HERANÇA] PEDIDOUNICO HERDA DE PEDIDO
# ================================
class PedidoUnico(Pedido):
def tempo_restante_str(self):
tempo_decorrido = self.get_tempo_decorrido()
restante = self.TEMPO_BASE - tempo_decorrido
if restante <= 0:
return "PRONTO!"
minutos = int(restante // 60)
segundos = int(restante % 60)
return f"{minutos:02d}:{segundos:02d}"
# ================================
# CLASSE GERENCIADOR (COMPOSIÇÃO)
# ================================
class GerenciadorPedidos:
def __init__(self):
self._pedidos = [] # Lista de objetos
def adicionar(self, pedido):
self._pedidos.append(pedido)
def remover(self, pedido):
if pedido in self._pedidos:
self._pedidos.remove(pedido)
def listar(self):
# Ordena: Mais novos no topo da fila (menor tempo decorrido)
return sorted(self._pedidos.copy(), key=lambda p: p.get_tempo_decorrido())
# ================================
# PADRÃO FACTORY (FÁBRICA)
# ================================
class FabricaPedidos:
@staticmethod
def criar_pedido(mesa, pedido_texto):
# Encapsula a lógica de criação do objeto
return PedidoUnico(mesa, pedido_texto)
# ================================
# INTERFACE GRÁFICA (VIEW)
# ================================
class TemaApp:
def __init__(self):
self.cor_principal = "#4D4D4D"
self.cor_destaque = "#FF7F50"
self.cor_fundo = "#2E2E2E"
self.cor_texto = "#FFFFFF"
self.cor_sucesso = "#32CD32"
self.cor_erro = "#FF6347"
class JanelaBase(ctk.CTkToplevel):
def __init__(self, parent, titulo, largura, altura, tema):
super().__init__(parent)
self.tema = tema
self.title(titulo)
self.geometry(f"{largura}x{altura}")
self.configure(fg_color=self.tema.cor_fundo)
self.transient(parent)
# ============================================
# APP PRINCIPAL
# ============================================
ctk.set_appearance_mode("dark")
tema = TemaApp()
estoque = Estoque() # Objeto Estoque
gerenciador = GerenciadorPedidos() # Objeto Gerenciador
app = ctk.CTk()
app.title("Pastelaria Paulista — OOP Avançado")
app.geometry("900x600")
fonte_padrao = ctk.CTkFont(family="Segoe UI", size=16)
fonte_titulo = ctk.CTkFont(family="Segoe UI", size=40)
menu_lateral = ctk.CTkFrame(app, width=160, fg_color=tema.cor_principal)
menu_lateral.pack(side="left", fill="y")
conteudo_principal = ctk.CTkFrame(app, fg_color=tema.cor_fundo)
conteudo_principal.pack(side="left", fill="both", expand=True)
titulo = ctk.CTkLabel(conteudo_principal, text="PASTELARIA PAULISTA", font=fonte_titulo)
titulo.pack(pady=40)
# ================================
# BALCÃO
# ================================
def abrir_balcao():
janela_balcao = JanelaBase(app, "Balcão", 500, 450, tema)
ctk.CTkLabel(janela_balcao, text="Local:", font=fonte_padrao).pack(pady=10)
mesas = ctk.CTkComboBox(
janela_balcao,
values=["Mesa 1", "Mesa 2", "Mesa 3", "Mesa 4", "Mesa 5", "Mesa 6", "Balcão", "Estufa"],
font=ctk.CTkFont(size=18, weight="bold")
)
mesas.pack(pady=5)
ctk.CTkLabel(janela_balcao, text="Detalhes do Pedido:", font=fonte_padrao).pack(pady=10)
anotar_pedido = ctk.CTkTextbox(janela_balcao, height=100, width=400)
anotar_pedido.pack(pady=5)
def confirmar():
mesa = mesas.get()
texto = anotar_pedido.get("1.0", "end").strip()
if texto:
# Uso da Fábrica para criar o Objeto
pedido = FabricaPedidos.criar_pedido(mesa, texto)
gerenciador.adicionar(pedido)
anotar_pedido.delete("1.0", "end")
atualizar_cozinha()
ctk.CTkButton(
janela_balcao, text="Confirmar Pedido",
fg_color=tema.cor_sucesso, command=confirmar
).pack(pady=20)
# ================================
# COZINHA (LÓGICA AUTOMÁTICA)
# ================================
janela_cozinha = None
container_pedidos = None
def atualizar_cozinha():
global janela_cozinha, container_pedidos
if janela_cozinha is None or not janela_cozinha.winfo_exists():
return
# Limpa a interface antes de redesenhar
for widget in container_pedidos.winfo_children():
widget.destroy()
# Itera sobre os OBJETOS Pedido
for pedido in gerenciador.listar():
card = ctk.CTkFrame(container_pedidos, fg_color="#3B3B3B", corner_radius=15)
card.pack(pady=10, padx=10, fill="x")
ctk.CTkLabel(card, text=f"{pedido.mesa}",
font=ctk.CTkFont(size=20, weight="bold"),
text_color="#FFB357").pack(anchor="w", padx=10)
ctk.CTkLabel(card, text=pedido.pedido_texto,
font=ctk.CTkFont(size=16), text_color="white"
).pack(anchor="w", padx=10)
label_timer = ctk.CTkLabel(card, text="⏱️", font=ctk.CTkFont(size=18))
label_timer.pack(anchor="w", padx=10, pady=5)
# ====== TIMER AUTOMÁTICO ======
def atualizar_label(label, pedido_obj):
# Verifica se o pedido ainda existe no gerenciador
if pedido_obj not in gerenciador.listar():
return
# Verifica se o tempo acabou
if pedido_obj.acabou_tempo():
# AUTOMAÇÃO: Remove o pedido e atualiza a tela
gerenciador.remover(pedido_obj)
atualizar_cozinha()
return
# Atualiza texto (Polimorfismo: chama método do objeto)
tempo_str = pedido_obj.tempo_restante_str()
label.configure(text=f"⏱ {tempo_str}")
# Lógica de Cores
tempo_limpo = tempo_str.replace("⚡", "").strip()
if ":" in tempo_limpo:
minutos, segundos = map(int, tempo_limpo.split(":"))
total = minutos * 60 + segundos
if total < 60: label.configure(text_color="#FF0000") # Vermelho
elif total < 300: label.configure(text_color="#FFFF00") # Amarelo
else: label.configure(text_color="#00FF00") # Verde
# Chama a função novamente em 1 segundo
label.after(1000, lambda: atualizar_label(label, pedido_obj))
atualizar_label(label_timer, pedido)
def abrir_cozinha():
global janela_cozinha, container_pedidos
if janela_cozinha is None or not janela_cozinha.winfo_exists():
janela_cozinha = JanelaBase(app, "Cozinha", 700, 700, tema)
container_pedidos = ctk.CTkFrame(janela_cozinha, fg_color=tema.cor_fundo)
container_pedidos.pack(fill="both", expand=True, padx=20, pady=20)
atualizar_cozinha()
# ================================
# ESTOQUE (INTERFACE)
# ================================
def abrir_estoque():
janela_estoque = JanelaBase(app, "Estoque", 700, 600, tema)
frame_principal = ctk.CTkFrame(janela_estoque)
frame_principal.pack(pady=20, padx=20, fill="both", expand=True)
frame_inputs = ctk.CTkFrame(frame_principal)
frame_inputs.pack(fill="x", pady=10)
ctk.CTkLabel(frame_inputs, text="Nome do Produto:").pack(anchor="w", padx=10)
entry_nome = ctk.CTkEntry(frame_inputs, width=300)
entry_nome.pack(pady=5, padx=10)
ctk.CTkLabel(frame_inputs, text="Quantidade:").pack(anchor="w", padx=10)
entry_qtd = ctk.CTkEntry(frame_inputs, width=300)
entry_qtd.pack(pady=5, padx=10)
frame_lista = ctk.CTkFrame(frame_principal)
frame_lista.pack(fill="both", expand=True, pady=10)
def atualizar_lista():
for widget in frame_lista.winfo_children():
widget.destroy()
produtos = estoque.listar() # Retorna lista de OBJETOS
for prod in produtos:
linha = ctk.CTkFrame(frame_lista)
linha.pack(fill="x", pady=3)
# Acessa atributo do objeto (prod.nome)
ctk.CTkLabel(linha, text=f"{prod.nome} — {prod.quantidade}").pack(side="left", padx=5)
ctk.CTkButton(
linha, text="Editar", width=80, fg_color="#1E90FF",
command=lambda p=prod: editar_produto(p)
).pack(side="right", padx=5)
def editar_produto(produto):
janela_editar = JanelaBase(janela_estoque, "Editar Produto", 350, 250, tema)
ctk.CTkLabel(janela_editar, text=f"Produto: {produto.nome}", font=ctk.CTkFont(size=18)).pack(pady=10)
ctk.CTkLabel(janela_editar, text="Nova Quantidade:").pack()
entry_nova_qtd = ctk.CTkEntry(janela_editar, width=200)
entry_nova_qtd.insert(0, str(produto.quantidade))
entry_nova_qtd.pack(pady=10)
def salvar_edicao():
nova_qtd = entry_nova_qtd.get().strip()
if nova_qtd.isdigit():
# Usa o setter do objeto (Encapsulamento)
produto.quantidade = int(nova_qtd)
estoque.adicionar_produto(produto)
atualizar_lista()
janela_editar.destroy()
ctk.CTkButton(janela_editar, text="Salvar", fg_color=tema.cor_sucesso, command=salvar_edicao).pack(pady=10)
def acao_adicionar():
nome = entry_nome.get().strip()
qtd = entry_qtd.get().strip()
if nome and qtd.isdigit():
# Criação do OBJETO Produto
novo_prod = Produto(nome, int(qtd))
estoque.adicionar_produto(novo_prod)
atualizar_lista()
def acao_excluir():
nome = entry_nome.get().strip()
if nome:
estoque.excluir_produto(nome)
atualizar_lista()
ctk.CTkButton(frame_inputs, text="Adicionar", fg_color=tema.cor_sucesso, command=acao_adicionar).pack(pady=5)
ctk.CTkButton(frame_inputs, text="Excluir", fg_color=tema.cor_erro, command=acao_excluir).pack(pady=5)
atualizar_lista()
# LIGAÇÃO MENU
ctk.CTkButton(menu_lateral, text="Estoque", command=abrir_estoque, fg_color=tema.cor_destaque).pack(pady=10)
ctk.CTkButton(menu_lateral, text="Cozinha", command=abrir_cozinha, fg_color=tema.cor_destaque).pack(pady=10)
ctk.CTkButton(menu_lateral, text="Balcão", command=abrir_balcao, fg_color=tema.cor_destaque).pack(pady=10)
app.mainloop()