diff --git a/Guia5/README.md b/Guia5/README.md index 7c18aef..767d513 100644 --- a/Guia5/README.md +++ b/Guia5/README.md @@ -1,80 +1,127 @@ -# Guia 5 — Projeto de Sistema com Orientação a Objetos - -## Contexto - -Agora é sua vez de propor e implementar um **sistema completo** utilizando o paradigma de Orientação a Objetos em Python. - -O objetivo deste guia é consolidar os conceitos vistos no curso de POO: classes, objetos, construtores, métodos, encapsulamento, atributos de classe/métodos de classe, herança, polimorfismo, classes abstratas, interfaces (via ABC), composição, etc. - -**Exemplos de sistemas sugeridos** (escolha um ou crie o seu): -- Sistema de Banco (conta corrente, poupança, transferência, cliente) -- Sistema de Gerenciamento de Biblioteca -- Sistema de Loja/E-commerce simples (Produto, Carrinho, Cliente, Pedido) -- Sistema de Escola (Aluno, Professor, Disciplina, Turma) -- Sistema de Hospital (Paciente, Médico, Consulta, Exame) - -Toda o projeto deve estar explicado conceitualmente em um arquivo Guia5__README.md contendo o que se segue: - ---- +# Guia 5 — Sistema de Gerenciamento de Biblioteca ## 1. Diagrama UML ### Diagrama de Classes Principal -Mantenha o diagrama **exatamente como está** (atualize apenas se necessário). +O diagrama abaixo foi adaptado a partir do modelo de exemplo (Cliente/Conta) para o domínio de biblioteca: `Pessoa` e `Item` são as classes abstratas que concentram o comportamento comum, `Usuario`/`Bibliotecario` e `Livro`/`Revista` são suas especializações, e `Biblioteca` e `Emprestimo` representam a composição que orquestra o sistema. ```mermaid classDiagram - class Cliente { + class Pessoa { + <> -nome: str -cpf: str +__init__(nome, cpf) + +apresentar() str +__str__() str } - class Conta { + class Usuario { + -limite_emprestimos: int + -emprestimos_ativos: list + +pode_retirar() bool + +registrar_emprestimo(emprestimo) + +registrar_devolucao(emprestimo) + +apresentar() str + } + class Bibliotecario { + -matricula: str + +apresentar() str + } + + class Item { <> - -numero: int - -saldo: float - +depositar(valor) - +sacar(valor) - +transferir(destino, valor) + -titulo: str + -codigo: int + -disponivel: bool + +marcar_como_emprestado() + +marcar_como_devolvido() + +prazo_emprestimo_dias() int + +descricao_detalhada() str } - class ContaCorrente { - -limite: float + class Livro { + -autor: str + -paginas: int + +prazo_emprestimo_dias() int + +descricao_detalhada() str } - class ContaPoupanca { - -taxa_rendimento: float + class Revista { + -edicao: int + +prazo_emprestimo_dias() int + +descricao_detalhada() str } - Cliente "1" --> "1..*" Conta : possui - Conta <|-- ContaCorrente - Conta <|-- ContaPoupanca + + class Emprestimo { + -data_emprestimo: date + -data_devolucao_prevista: date + -data_devolucao_efetiva: date + +esta_ativo() bool + +esta_atrasado(data) bool + +finalizar(data) + } + + class Biblioteca { + -nome: str + +adicionar_item(item) + +cadastrar_usuario(usuario) + +realizar_emprestimo(cpf, codigo) Emprestimo + +realizar_devolucao(cpf, codigo) Emprestimo + +itens_disponiveis() list + +emprestimos_atrasados() list + } + + Pessoa <|-- Usuario + Pessoa <|-- Bibliotecario + Item <|-- Livro + Item <|-- Revista + + Usuario "1" --> "0..*" Emprestimo : possui + Emprestimo "1" --> "1" Item : refere-se a + + Biblioteca "1" *-- "0..*" Item : acervo + Biblioteca "1" *-- "0..*" Usuario : usuários + Biblioteca "1" *-- "0..*" Bibliotecario : equipe + Biblioteca "1" *-- "0..*" Emprestimo : histórico ``` +### Justificativa das alterações em relação ao diagrama de exemplo + +- **Hierarquia dupla:** o domínio bancário tinha apenas uma hierarquia (`Conta`). Aqui foram criadas duas hierarquias de herança — `Pessoa` (→ `Usuario`, `Bibliotecario`) e `Item` (→ `Livro`, `Revista`) — para exercitar herança e polimorfismo em mais de um eixo do sistema. +- **Composição explícita:** a classe `Biblioteca` foi adicionada como agregadora central (não existia equivalente direto no exemplo), demonstrando composição com múltiplas classes (`Item`, `Usuario`, `Bibliotecario`, `Emprestimo`). +- **Classe de associação:** `Emprestimo` substitui o papel que `transferir()` cumpria na `Conta` do exemplo — em vez de um método, o vínculo "usuário pegou um item emprestado" virou uma classe própria, permitindo guardar datas e status sem inflar `Usuario` ou `Item`. +- **Polimorfismo de regra de negócio:** `prazo_emprestimo_dias()` é abstrato em `Item` e implementado de forma diferente em `Livro` (14 dias) e `Revista` (7 dias), papel análogo ao que `depositar`/`sacar` cumpriam em `Conta`/`ContaCorrente`/`ContaPoupanca`. + --- -## Descreva as Classes (Exemplos) +## 2. Descreva as Classes -Cliente: Representa o titular da conta. -Conta (abstrata): Define o contrato comum (depósito, saque, transferência). -ContaCorrente: Herda de Conta, permite saldo negativo até o limite. -ContaPoupanca: Herda de Conta, aplica rendimento. +- **Pessoa** *(abstrata)*: define o contrato comum entre qualquer pessoa do sistema (nome, CPF, validações e o método abstrato `apresentar()`). +- **Usuario**: herda de `Pessoa`. Pode retirar itens por empréstimo até um limite configurável; mantém a lista de seus empréstimos ativos (composição). +- **Bibliotecario**: herda de `Pessoa`. Representa o funcionário responsável pela operação do sistema; possui matrícula própria. +- **Item** *(abstrata)*: define o contrato comum entre qualquer item do acervo (título, código único, disponibilidade) e exige que cada subclasse implemente seu próprio prazo de empréstimo e descrição detalhada. +- **Livro**: herda de `Item`. Possui autor, número de páginas e prazo de devolução de 14 dias. +- **Revista**: herda de `Item`. Possui número de edição e prazo de devolução de 7 dias. +- **Emprestimo**: representa o vínculo (composição) entre um `Usuario` e um `Item` durante o período de retirada; calcula a data prevista de devolução automaticamente a partir do prazo do item, e sabe informar se está ativo ou atrasado. +- **Biblioteca**: classe central do sistema. Por composição, mantém o acervo de itens, os usuários, os bibliotecários e o histórico de empréstimos, além de implementar as regras de negócio (`realizar_emprestimo`, `realizar_devolucao`, buscas e consultas). --- -## Use este esquema de pastas como Exemplo +## 3. Esquema de Pastas + ```bash -meu-sistema-banco/ +Guia5/ ├── src/ │ ├── __init__.py -│ ├── cliente.py -│ ├── conta.py -│ ├── conta_corrente.py -│ └── conta_poupanca.py -├── tests/ -│ ├── __init__.py -│ ├── test_cliente.py -│ ├── test_conta_corrente.py -│ └── test_sistema_banco.py +│ ├── pessoa.py +│ ├── usuario.py +│ ├── bibliotecario.py +│ ├── item.py +│ ├── livro.py +│ ├── revista.py +│ ├── emprestimo.py +│ └── biblioteca.py +├── main.py +├── pytest.ini ├── requirements.txt ├── README.md └── .gitignore @@ -82,14 +129,11 @@ meu-sistema-banco/ --- -## Descreva como preparar o ambiente - -Siga rigorosamente uma sequência e descreva ela nesta seção para garantir reprodutibilidade. Exemplo: - +## 4. Preparando o Ambiente ### 1. Criar venv -Na pasta do projeto ..\Guia5> executar o comando: +Na pasta do projeto `..\Guia5>` executar o comando: ```bash python -m venv .venv @@ -99,51 +143,37 @@ python -m venv .venv #### Windows -Na pasta do projeto ..\Guia5> executar o comando: +Na pasta do projeto `..\Guia5>` executar o comando: ```bash .\.venv\Scripts\activate ``` -#### Linux/macOS - -Na pasta do projeto ..\Guia5> executar o comando: - -```bash -source .venv/bin/activate -``` - ### 3. Instalar dependências -Na pasta do projeto ..\Guia5> executar o comando: +Na pasta do projeto `..\Guia5>` executar o comando: ```bash pip install -r requirements.txt ``` -### 4. Testes - -Na pasta do projeto ..\Guia5> executar o comando: +### 4. Execução - -```bash -pytest -v -``` - -ou - -```bash -python -m pytest -v -``` - -### 5. Execução -Na pasta do projeto ..\Guia5> executar o comando: +Na pasta do projeto `..\Guia5>` executar o comando: ```bash python main.py ``` -Na tela que abrir você poderá interagir com o sistema da seguinte forma... (descreva funcionalidades, o que o usuário pode experimentar, etc.). - ---- - +Na tela que abrir, o sistema já inicia com alguns dados de exemplo (2 livros, 1 revista e 1 usuária cadastrados) e você poderá interagir através de um menu numérico no terminal, podendo: + +1. **Listar acervo** — vê todos os livros e revistas cadastrados, com status de disponibilidade. +2. **Listar usuários** — vê os usuários cadastrados e quantos empréstimos ativos cada um possui. +3. **Cadastrar usuário** — informa nome e CPF (11 dígitos) para criar um novo usuário. +4. **Adicionar livro ao acervo** — informa título, autor e número de páginas. +5. **Adicionar revista ao acervo** — informa título e número da edição. +6. **Realizar empréstimo** — informa o CPF do usuário e o código do item; o sistema calcula automaticamente a data prevista de devolução. +7. **Realizar devolução** — informa o CPF do usuário e o código do item para finalizar o empréstimo. +8. **Listar empréstimos ativos** — mostra todos os empréstimos em andamento. +9. **Listar empréstimos atrasados** — mostra apenas os empréstimos cuja data prevista de devolução já passou. +0. **Sair** — encerra o programa. \ No newline at end of file diff --git a/Guia5/main.py b/Guia5/main.py index 6222e1f..5f7dfa6 100644 --- a/Guia5/main.py +++ b/Guia5/main.py @@ -1,7 +1,150 @@ -from Guia5.src import * +from datetime import date +from src import Biblioteca, Usuario, Bibliotecario, Livro, Revista + + +def montar_biblioteca_exemplo() -> Biblioteca: + """Cria uma biblioteca com alguns dados de exemplo, para facilitar os testes manuais.""" + biblioteca = Biblioteca("Biblioteca Municipal Aracati") + + biblioteca.adicionar_item(Livro("O Cortiço", "Aluísio Azevedo", 256)) + biblioteca.adicionar_item(Livro("Dom Casmurro", "Machado de Assis", 208)) + biblioteca.adicionar_item(Revista("Superinteressante", edicao=412)) + + biblioteca.cadastrar_usuario(Usuario("Maria Silva", "12345678901")) + biblioteca.cadastrar_bibliotecario(Bibliotecario("João Pedro", "10987654321", "BIB-001")) + + return biblioteca + + +def exibir_menu() -> None: + print("\n===== SISTEMA DE GERENCIAMENTO DE BIBLIOTECA =====") + print("1. Listar acervo") + print("2. Listar usuários") + print("3. Cadastrar usuário") + print("4. Adicionar livro ao acervo") + print("5. Adicionar revista ao acervo") + print("6. Realizar empréstimo") + print("7. Realizar devolução") + print("8. Listar empréstimos ativos") + print("9. Listar empréstimos atrasados") + print("0. Sair") + + +def listar_acervo(biblioteca: Biblioteca) -> None: + if not biblioteca.acervo: + print("O acervo está vazio.") + return + for item in biblioteca.acervo: + print(f"- {item} | {item.descricao_detalhada()}") + + +def listar_usuarios(biblioteca: Biblioteca) -> None: + if not biblioteca.usuarios: + print("Nenhum usuário cadastrado.") + return + for usuario in biblioteca.usuarios: + print(f"- {usuario.apresentar()}") + + +def cadastrar_usuario(biblioteca: Biblioteca) -> None: + nome = input("Nome do usuário: ") + cpf = input("CPF (11 dígitos): ") + try: + biblioteca.cadastrar_usuario(Usuario(nome, cpf)) + print("Usuário cadastrado com sucesso!") + except (ValueError, TypeError) as erro: + print(f"Erro: {erro}") + + +def adicionar_livro(biblioteca: Biblioteca) -> None: + titulo = input("Título do livro: ") + autor = input("Autor: ") + try: + paginas = int(input("Número de páginas: ")) + biblioteca.adicionar_item(Livro(titulo, autor, paginas)) + print("Livro adicionado ao acervo!") + except (ValueError, TypeError) as erro: + print(f"Erro: {erro}") + + +def adicionar_revista(biblioteca: Biblioteca) -> None: + titulo = input("Título da revista: ") + try: + edicao = int(input("Número da edição: ")) + biblioteca.adicionar_item(Revista(titulo, edicao)) + print("Revista adicionada ao acervo!") + except (ValueError, TypeError) as erro: + print(f"Erro: {erro}") + + +def realizar_emprestimo(biblioteca: Biblioteca) -> None: + cpf = input("CPF do usuário: ") + try: + codigo = int(input("Código do item: ")) + emprestimo = biblioteca.realizar_emprestimo(cpf, codigo) + print(f"Empréstimo realizado! Devolução prevista para {emprestimo.data_devolucao_prevista}.") + except (ValueError, TypeError, RuntimeError, PermissionError) as erro: + print(f"Erro: {erro}") + + +def realizar_devolucao(biblioteca: Biblioteca) -> None: + cpf = input("CPF do usuário: ") + try: + codigo = int(input("Código do item: ")) + biblioteca.realizar_devolucao(cpf, codigo) + print("Devolução registrada com sucesso!") + except (ValueError, TypeError, RuntimeError) as erro: + print(f"Erro: {erro}") + + +def listar_emprestimos_ativos(biblioteca: Biblioteca) -> None: + ativos = [e for e in biblioteca.emprestimos if e.esta_ativo] + if not ativos: + print("Não há empréstimos ativos.") + return + for emprestimo in ativos: + print(f"- {emprestimo}") + + +def listar_emprestimos_atrasados(biblioteca: Biblioteca) -> None: + atrasados = biblioteca.emprestimos_atrasados(date.today()) + if not atrasados: + print("Não há empréstimos atrasados.") + return + for emprestimo in atrasados: + print(f"- {emprestimo}") + def main(): - pass + biblioteca = montar_biblioteca_exemplo() + print(f"Sistema iniciado: {biblioteca}") + + acoes = { + "1": listar_acervo, + "2": listar_usuarios, + "3": cadastrar_usuario, + "4": adicionar_livro, + "5": adicionar_revista, + "6": realizar_emprestimo, + "7": realizar_devolucao, + "8": listar_emprestimos_ativos, + "9": listar_emprestimos_atrasados, + } + + while True: + exibir_menu() + opcao = input("Escolha uma opção: ").strip() + + if opcao == "0": + print("Encerrando o sistema. Até logo!") + break + + acao = acoes.get(opcao) + if acao is None: + print("Opção inválida. Tente novamente.") + continue + + acao(biblioteca) if __name__ == "__main__": diff --git a/Guia5/src/__init__.py b/Guia5/src/__init__.py index e69de29..627159c 100644 --- a/Guia5/src/__init__.py +++ b/Guia5/src/__init__.py @@ -0,0 +1,19 @@ +from src.pessoa import Pessoa +from src.usuario import Usuario +from src.bibliotecario import Bibliotecario +from src.item import Item +from src.livro import Livro +from src.revista import Revista +from src.emprestimo import Emprestimo +from src.biblioteca import Biblioteca + +__all__ = [ + "Pessoa", + "Usuario", + "Bibliotecario", + "Item", + "Livro", + "Revista", + "Emprestimo", + "Biblioteca", +] diff --git a/Guia5/src/biblioteca.py b/Guia5/src/biblioteca.py new file mode 100644 index 0000000..994c5b4 --- /dev/null +++ b/Guia5/src/biblioteca.py @@ -0,0 +1,131 @@ +from datetime import date +from src.bibliotecario import Bibliotecario +from src.emprestimo import Emprestimo +from src.item import Item +from src.usuario import Usuario + + +class Biblioteca: + """ + Classe central do sistema. Mantém o acervo de itens, os usuários + cadastrados e o histórico de empréstimos, coordenando as regras de + negócio entre eles por meio de composição. + """ + + def __init__(self, nome: str): + if not nome or not nome.strip(): + raise ValueError("O nome da biblioteca não pode ser vazio.") + + self._nome = nome.strip() + self._acervo = [] # composição: Biblioteca possui Itens + self._usuarios = [] # composição: Biblioteca possui Usuarios + self._bibliotecarios = [] # composição: Biblioteca possui Bibliotecarios + self._emprestimos = [] # composição: Biblioteca possui Emprestimos + + @property + def nome(self) -> str: + return self._nome + + @property + def acervo(self) -> list: + return list(self._acervo) + + @property + def usuarios(self) -> list: + return list(self._usuarios) + + @property + def emprestimos(self) -> list: + return list(self._emprestimos) + + # ---------- Cadastro ---------- + + def adicionar_item(self, item: Item) -> None: + if not isinstance(item, Item): + raise TypeError("Apenas instâncias de Item podem ser adicionadas ao acervo.") + self._acervo.append(item) + + def cadastrar_usuario(self, usuario: Usuario) -> None: + if not isinstance(usuario, Usuario): + raise TypeError("Apenas instâncias de Usuario podem ser cadastradas.") + if usuario in self._usuarios: + raise ValueError(f"Usuário com CPF {usuario.cpf} já está cadastrado.") + self._usuarios.append(usuario) + + def cadastrar_bibliotecario(self, bibliotecario: Bibliotecario) -> None: + if not isinstance(bibliotecario, Bibliotecario): + raise TypeError("Apenas instâncias de Bibliotecario podem ser cadastradas.") + self._bibliotecarios.append(bibliotecario) + + # ---------- Consulta ---------- + + def buscar_item_por_codigo(self, codigo: int) -> Item: + for item in self._acervo: + if item.codigo == codigo: + return item + raise ValueError(f"Nenhum item encontrado com o código {codigo}.") + + def buscar_usuario_por_cpf(self, cpf: str) -> Usuario: + for usuario in self._usuarios: + if usuario.cpf == cpf: + return usuario + raise ValueError(f"Nenhum usuário encontrado com o CPF {cpf}.") + + def itens_disponiveis(self) -> list: + return [item for item in self._acervo if item.disponivel] + + def emprestimos_atrasados(self, data_referencia: date = None) -> list: + return [ + emp for emp in self._emprestimos + if emp.esta_atrasado(data_referencia) + ] + + # ---------- Regras de negócio ---------- + + def realizar_emprestimo(self, cpf_usuario: str, codigo_item: int) -> Emprestimo: + usuario = self.buscar_usuario_por_cpf(cpf_usuario) + item = self.buscar_item_por_codigo(codigo_item) + + if not item.disponivel: + raise RuntimeError(f"O item '{item.titulo}' não está disponível para empréstimo.") + if not usuario.pode_retirar(): + raise PermissionError( + f"{usuario.nome} já atingiu o limite de {usuario.limite_emprestimos} empréstimos." + ) + + emprestimo = Emprestimo(usuario, item) + item.marcar_como_emprestado() + usuario.registrar_emprestimo(emprestimo) + self._emprestimos.append(emprestimo) + + return emprestimo + + def realizar_devolucao(self, cpf_usuario: str, codigo_item: int) -> Emprestimo: + usuario = self.buscar_usuario_por_cpf(cpf_usuario) + item = self.buscar_item_por_codigo(codigo_item) + + emprestimo = next( + ( + emp for emp in self._emprestimos + if emp.esta_ativo and emp.usuario == usuario and emp.item.codigo == item.codigo + ), + None, + ) + + if emprestimo is None: + raise ValueError( + f"Não há empréstimo ativo do item '{item.titulo}' para {usuario.nome}." + ) + + emprestimo.finalizar() + item.marcar_como_devolvido() + usuario.registrar_devolucao(emprestimo) + + return emprestimo + + def __str__(self) -> str: + return ( + f"Biblioteca '{self._nome}' | Acervo: {len(self._acervo)} itens | " + f"Usuários: {len(self._usuarios)} | Empréstimos ativos: " + f"{sum(1 for e in self._emprestimos if e.esta_ativo)}" + ) diff --git a/Guia5/src/bibliotecario.py b/Guia5/src/bibliotecario.py new file mode 100644 index 0000000..1c1cad6 --- /dev/null +++ b/Guia5/src/bibliotecario.py @@ -0,0 +1,22 @@ +from src.pessoa import Pessoa + +class Bibliotecario(Pessoa): + """ + Representa um funcionário responsável por operar o acervo: cadastrar + itens, registrar empréstimos e devoluções dentro da biblioteca. + """ + + def __init__(self, nome: str, cpf: str, matricula: str): + super().__init__(nome, cpf) + + if not matricula or not matricula.strip(): + raise ValueError("A matrícula não pode ser vazia.") + + self._matricula = matricula.strip() + + @property + def matricula(self) -> str: + return self._matricula + + def apresentar(self) -> str: + return f"Bibliotecário: {self._nome} | Matrícula: {self._matricula}" diff --git a/Guia5/src/emprestimo.py b/Guia5/src/emprestimo.py new file mode 100644 index 0000000..2a2c5b2 --- /dev/null +++ b/Guia5/src/emprestimo.py @@ -0,0 +1,68 @@ +from datetime import date, timedelta + +from src.item import Item +from src.usuario import Usuario + + +class Emprestimo: + """ + Representa o vínculo entre um Usuario e um Item enquanto o + empréstimo está em vigor. É criado por composição dentro da + Biblioteca e referenciado também pelo Usuario correspondente. + """ + + def __init__(self, usuario: Usuario, item: Item, data_emprestimo: date = None): + if not isinstance(usuario, Usuario): + raise TypeError("O parâmetro 'usuario' deve ser uma instância de Usuario.") + if not isinstance(item, Item): + raise TypeError("O parâmetro 'item' deve ser uma instância de Item.") + + self._usuario = usuario + self._item = item + self._data_emprestimo = data_emprestimo or date.today() + self._data_devolucao_prevista = self._data_emprestimo + timedelta( + days=item.prazo_emprestimo_dias() + ) + self._data_devolucao_efetiva = None + + @property + def usuario(self) -> Usuario: + return self._usuario + + @property + def item(self) -> Item: + return self._item + + @property + def data_emprestimo(self) -> date: + return self._data_emprestimo + + @property + def data_devolucao_prevista(self) -> date: + return self._data_devolucao_prevista + + @property + def data_devolucao_efetiva(self) -> date: + return self._data_devolucao_efetiva + + @property + def esta_ativo(self) -> bool: + return self._data_devolucao_efetiva is None + + def esta_atrasado(self, data_referencia: date = None) -> bool: + if not self.esta_ativo: + return False + data_referencia = data_referencia or date.today() + return data_referencia > self._data_devolucao_prevista + + def finalizar(self, data_devolucao: date = None) -> None: + if not self.esta_ativo: + raise RuntimeError("Este empréstimo já foi finalizado.") + self._data_devolucao_efetiva = data_devolucao or date.today() + + def __str__(self) -> str: + status = "Ativo" if self.esta_ativo else "Finalizado" + return ( + f"Empréstimo de '{self._item.titulo}' para {self._usuario.nome} " + f"(previsto: {self._data_devolucao_prevista.isoformat()}, status: {status})" + ) \ No newline at end of file diff --git a/Guia5/src/item.py b/Guia5/src/item.py new file mode 100644 index 0000000..d962697 --- /dev/null +++ b/Guia5/src/item.py @@ -0,0 +1,60 @@ +from abc import ABC, abstractmethod + + +class Item(ABC): + """ + Classe abstrata que representa qualquer item do acervo da biblioteca. + + Define o contrato comum entre Livro e Revista, incluindo o controle + de disponibilidade e a regra polimórfica de prazo de devolução, que + cada subclasse deve implementar de acordo com sua própria natureza. + """ + + _proximo_codigo = 1 # atributo de classe: gera códigos únicos sequenciais + + def __init__(self, titulo: str, codigo: int = None): + if not titulo or not titulo.strip(): + raise ValueError("O título não pode ser vazio.") + + self._titulo = titulo.strip() + self._disponivel = True + + if codigo is None: + self._codigo = Item._proximo_codigo + Item._proximo_codigo += 1 + else: + self._codigo = codigo + + @property + def titulo(self) -> str: + return self._titulo + + @property + def codigo(self) -> int: + return self._codigo + + @property + def disponivel(self) -> bool: + return self._disponivel + + def marcar_como_emprestado(self) -> None: + if not self._disponivel: + raise RuntimeError(f"O item '{self._titulo}' já está emprestado.") + self._disponivel = False + + def marcar_como_devolvido(self) -> None: + self._disponivel = True + + @abstractmethod + def prazo_emprestimo_dias(self) -> int: + """Cada tipo de item define seu próprio prazo padrão de devolução.""" + raise NotImplementedError + + @abstractmethod + def descricao_detalhada(self) -> str: + """Cada tipo de item descreve seus próprios detalhes específicos.""" + raise NotImplementedError + + def __str__(self) -> str: + status = "Disponível" if self._disponivel else "Emprestado" + return f"[{self._codigo}] {self._titulo} ({status})" diff --git a/Guia5/src/livro.py b/Guia5/src/livro.py new file mode 100644 index 0000000..fe3c38c --- /dev/null +++ b/Guia5/src/livro.py @@ -0,0 +1,35 @@ +from src.item import Item + + +class Livro(Item): + """ + Representa um livro do acervo. Possui prazo de empréstimo mais longo + e dados bibliográficos próprios (autor e número de páginas). + """ + + PRAZO_PADRAO_DIAS = 14 + + def __init__(self, titulo: str, autor: str, paginas: int, codigo: int = None): + super().__init__(titulo, codigo) + + if not autor or not autor.strip(): + raise ValueError("O autor não pode ser vazio.") + if paginas <= 0: + raise ValueError("O número de páginas deve ser maior que zero.") + + self._autor = autor.strip() + self._paginas = paginas + + @property + def autor(self) -> str: + return self._autor + + @property + def paginas(self) -> int: + return self._paginas + + def prazo_emprestimo_dias(self) -> int: + return self.PRAZO_PADRAO_DIAS + + def descricao_detalhada(self) -> str: + return f"Livro: {self._titulo} | Autor: {self._autor} | {self._paginas} páginas" \ No newline at end of file diff --git a/Guia5/src/pessoa.py b/Guia5/src/pessoa.py new file mode 100644 index 0000000..6072667 --- /dev/null +++ b/Guia5/src/pessoa.py @@ -0,0 +1,44 @@ +from abc import ABC, abstractmethod + + +class Pessoa(ABC): + """ + Classe abstrata que representa uma pessoa cadastrada no sistema. + + Define o contrato comum entre Usuario e Bibliotecario, centralizando + os atributos básicos (nome e CPF) e exigindo que toda subclasse + implemente seu próprio método de apresentação. + """ + + def __init__(self, nome: str, cpf: str): + if not nome or not nome.strip(): + raise ValueError("O nome não pode ser vazio.") + if not cpf or len(cpf.strip()) != 11 or not cpf.strip().isdigit(): + raise ValueError("O CPF deve conter exatamente 11 dígitos numéricos.") + + self._nome = nome.strip() + self._cpf = cpf.strip() + + @property + def nome(self) -> str: + return self._nome + + @property + def cpf(self) -> str: + return self._cpf + + @abstractmethod + def apresentar(self) -> str: + """Cada tipo de pessoa deve descrever seu papel no sistema.""" + raise NotImplementedError + + def __str__(self) -> str: + return f"{self._nome} (CPF: {self._cpf})" + + def __eq__(self, other) -> bool: + if not isinstance(other, Pessoa): + return NotImplemented + return self._cpf == other._cpf + + def __hash__(self) -> int: + return hash(self._cpf) \ No newline at end of file diff --git a/Guia5/src/revista.py b/Guia5/src/revista.py new file mode 100644 index 0000000..82d38c8 --- /dev/null +++ b/Guia5/src/revista.py @@ -0,0 +1,28 @@ +from src.item import Item + + +class Revista(Item): + """ + Representa uma revista do acervo. Possui prazo de empréstimo mais + curto que o livro, refletindo seu caráter de publicação periódica. + """ + + PRAZO_PADRAO_DIAS = 7 + + def __init__(self, titulo: str, edicao: int, codigo: int = None): + super().__init__(titulo, codigo) + + if edicao <= 0: + raise ValueError("O número da edição deve ser maior que zero.") + + self._edicao = edicao + + @property + def edicao(self) -> int: + return self._edicao + + def prazo_emprestimo_dias(self) -> int: + return self.PRAZO_PADRAO_DIAS + + def descricao_detalhada(self) -> str: + return f"Revista: {self._titulo} | Edição: {self._edicao}" \ No newline at end of file diff --git a/Guia5/src/usuario.py b/Guia5/src/usuario.py new file mode 100644 index 0000000..e12599f --- /dev/null +++ b/Guia5/src/usuario.py @@ -0,0 +1,44 @@ +from src.pessoa import Pessoa + + +class Usuario(Pessoa): + """ + Representa um usuário da biblioteca, capaz de retirar itens por + empréstimo, respeitando um limite máximo simultâneo. + """ + + LIMITE_EMPRESTIMOS_PADRAO = 3 + + def __init__(self, nome: str, cpf: str, limite_emprestimos: int = LIMITE_EMPRESTIMOS_PADRAO): + super().__init__(nome, cpf) + + if limite_emprestimos <= 0: + raise ValueError("O limite de empréstimos deve ser maior que zero.") + + self._limite_emprestimos = limite_emprestimos + self._emprestimos_ativos = [] # composição: Usuario possui Emprestimos + + @property + def limite_emprestimos(self) -> int: + return self._limite_emprestimos + + @property + def emprestimos_ativos(self) -> list: + return list(self._emprestimos_ativos) + + def pode_retirar(self) -> bool: + return len(self._emprestimos_ativos) < self._limite_emprestimos + + def registrar_emprestimo(self, emprestimo) -> None: + if not self.pode_retirar(): + raise PermissionError( + f"{self._nome} já atingiu o limite de {self._limite_emprestimos} empréstimos." + ) + self._emprestimos_ativos.append(emprestimo) + + def registrar_devolucao(self, emprestimo) -> None: + if emprestimo in self._emprestimos_ativos: + self._emprestimos_ativos.remove(emprestimo) + + def apresentar(self) -> str: + return f"Usuário: {self._nome} | Empréstimos ativos: {len(self._emprestimos_ativos)}/{self._limite_emprestimos}" \ No newline at end of file