diff --git a/Guia1/src/repositories/record_repository.py b/Guia1/src/repositories/record_repository.py index bded279..ff0e351 100644 --- a/Guia1/src/repositories/record_repository.py +++ b/Guia1/src/repositories/record_repository.py @@ -10,15 +10,34 @@ def __init__(self, file_path: str): def load_all(self): data = FileLoader.load_csv(self._file_path) - self._records = [ - Record(int(row["id"]), row["name"], row["address"]) - for row in data - ] + self._records = [] + + for row in data: + try: + if not row.get("id"): + raise ValueError("ID vazio") + + record_id = int(row["id"]) + + if record_id <= 0: + raise ValueError("ID negativo ou zero") + + if not row.get("name", "").strip() or not row.get("address", "").strip(): + raise ValueError("Nome ou endereço vazio") + + registro = Record(record_id, row["name"], row["address"]) + self._records.append(registro) + + except ValueError: + print(f"Registro inválido ignorado: {row}") + return self._records def search(self, term: str): - term = term.lower() + termos_busca = term.lower().split() + return [ r for r in self._records - if term in r.name.lower() or term in r.address.lower() - ] \ No newline at end of file + if all(t in f"{r.name} {r.address}".lower() for t in termos_busca) + ] + \ No newline at end of file diff --git a/Guia2/README.md b/Guia2/README.md new file mode 100644 index 0000000..56985d6 --- /dev/null +++ b/Guia2/README.md @@ -0,0 +1,155 @@ +# Guia 2 — Sistema de Folha de Pagamento (RH) + +## Contexto + +Você faz parte da equipe responsável por manter um módulo de **Folha de Pagamento** dentro de um sistema de **Recursos Humanos (RH)**. + +Esse módulo calcula informações básicas de funcionários de diferentes cargos, como: + +- nome +- matricula +- salario_base + +Atualmente o sistema possui uma hierarquia de classes para representar funcionários, mas carece implementar as subclasses de acordo com o diagrama UML abaixo: + + -------------------------------- + | Funcionario | + -------------------------------- + | - nome | + | - matricula | + | - salario_base | + -------------------------------- + | + dados_basicos() | + | + gerar_contracheque() | + | + calcular_salario_liquido() | + | # calcular_bonus() | + | # calcular_descontos() | + | # calcular_adicionais() | + -------------------------------- + ▲ + ----------------------------------------- + | | | + | | | + ----------------- ----------------- ----------------- + | Desenvolvedor | | Gerente | | Estagiari o | + ----------------- ----------------- ----------------- + | linguagem | | setor | | curso | + | senioridade | | qtd_equipe | | carga_horaria | + ----------------- ----------------- ----------------- + +Seu papel é implementar as subclasses passando nos testes propostos. + +--- + +## 1. Desenvolvedor + +### Atributos adicionais + +- `linguagem` +- `senioridade` + +### Regras + +#### `calcular_bonus()` + +O bônus depende da senioridade: + +| Senioridade | Bônus | +|-------------|----------------------| +| junior | 5% do salário base | +| pleno | 10% do salário base | +| senior | 15% do salário base | + +--- + +#### `calcular_descontos()` + +Desconto fixo de **8% do salário base**. + +--- + +#### `calcular_adicionais()` + +Adicional definido pela linguagem principal: + +| Linguagem | Adicional | +|----------------|-----------| +| Python | +500 | +| Java | +400 | +| JavaScript | +350 | +| Qualquer outra | +200 | + +--- + +## 2. Gerente + +### Atributos adicionais + +- `setor` +- `qtd_equipe` + +### Regras + +#### `calcular_bonus()` + +O bônus depende da quantidade de pessoas na equipe: + +| Tamanho da equipe | Bônus | +|-------------------|---------------------| +| até 5 | 10% do salário base | +| de 6 até 10 | 15% do salário base | +| acima de 10 | 20% do salário base | + +--- + +#### `calcular_descontos()` + +Desconto fixo de **12% do salário base**. + +--- + +#### `calcular_adicionais()` + +Adicional por responsabilidade: + +| Quantidade da equipe | Adicional | +|----------------------|-----------| +| equipe > 10 | +2000 | +| equipe > 5 | +1000 | +| caso contrário | +500 | + +--- + +## 3. Estagiario + +### Atributos adicionais + +- `curso` +- `carga_horaria` + +### Regras + +#### `calcular_bonus()` + +Bônus fixo de **3% do salário base**. + +--- + +#### `calcular_descontos()` + +Desconto fixo de **2% do salário base**. + +--- + +#### `calcular_adicionais()` + +Auxílio baseado na carga horária: + +| Carga horária | Adicional | +|---------------|-----------| +| até 20h | +150 | +| até 30h | +250 | +| acima de 30h | +350 | + +--- +995b5983a8b684cba7bbc7f802c760906f0d690c1e90bf440631cfe486082eb3 \ No newline at end of file diff --git a/Guia2/pytest.ini b/Guia2/pytest.ini new file mode 100644 index 0000000..97a0030 --- /dev/null +++ b/Guia2/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +pythonpath = src +testpaths = tests \ No newline at end of file diff --git a/Guia2/requirements.txt b/Guia2/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/Guia2/run_tests.bat b/Guia2/run_tests.bat new file mode 100644 index 0000000..a2937ce --- /dev/null +++ b/Guia2/run_tests.bat @@ -0,0 +1,2 @@ +pytest -v +pause \ No newline at end of file diff --git a/Guia2/src/__init__.py b/Guia2/src/__init__.py new file mode 100644 index 0000000..5d96e84 --- /dev/null +++ b/Guia2/src/__init__.py @@ -0,0 +1 @@ +from folha_pagamento import Estagiario, Gerente, Desenvolvedor, Funcionario diff --git a/Guia2/src/__pycache__/__init__.cpython-312.pyc b/Guia2/src/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..73e61be Binary files /dev/null and b/Guia2/src/__pycache__/__init__.cpython-312.pyc differ diff --git a/Guia2/src/folha_pagamento/__init__.py b/Guia2/src/folha_pagamento/__init__.py new file mode 100644 index 0000000..63a2be0 --- /dev/null +++ b/Guia2/src/folha_pagamento/__init__.py @@ -0,0 +1,4 @@ +from .funcionario import Funcionario +from .estagiario import Estagiario +from .desenvolvedor import Desenvolvedor +from .gerente import Gerente diff --git a/Guia2/src/folha_pagamento/__pycache__/__init__.cpython-312.pyc b/Guia2/src/folha_pagamento/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..c9423a9 Binary files /dev/null and b/Guia2/src/folha_pagamento/__pycache__/__init__.cpython-312.pyc differ diff --git a/Guia2/src/folha_pagamento/__pycache__/desenvolvedor.cpython-312.pyc b/Guia2/src/folha_pagamento/__pycache__/desenvolvedor.cpython-312.pyc new file mode 100644 index 0000000..4184750 Binary files /dev/null and b/Guia2/src/folha_pagamento/__pycache__/desenvolvedor.cpython-312.pyc differ diff --git a/Guia2/src/folha_pagamento/__pycache__/estagiario.cpython-312.pyc b/Guia2/src/folha_pagamento/__pycache__/estagiario.cpython-312.pyc new file mode 100644 index 0000000..065df0d Binary files /dev/null and b/Guia2/src/folha_pagamento/__pycache__/estagiario.cpython-312.pyc differ diff --git a/Guia2/src/folha_pagamento/__pycache__/funcionario.cpython-312.pyc b/Guia2/src/folha_pagamento/__pycache__/funcionario.cpython-312.pyc new file mode 100644 index 0000000..a87ac56 Binary files /dev/null and b/Guia2/src/folha_pagamento/__pycache__/funcionario.cpython-312.pyc differ diff --git a/Guia2/src/folha_pagamento/__pycache__/gerente.cpython-312.pyc b/Guia2/src/folha_pagamento/__pycache__/gerente.cpython-312.pyc new file mode 100644 index 0000000..444c7e2 Binary files /dev/null and b/Guia2/src/folha_pagamento/__pycache__/gerente.cpython-312.pyc differ diff --git a/Guia2/src/folha_pagamento/desenvolvedor.py b/Guia2/src/folha_pagamento/desenvolvedor.py new file mode 100644 index 0000000..01da94f --- /dev/null +++ b/Guia2/src/folha_pagamento/desenvolvedor.py @@ -0,0 +1,36 @@ +from folha_pagamento.funcionario import Funcionario + +# Desenvolva a classe Desenvolvedor aqui. + +class Desenvolvedor(Funcionario): + def _init_(self, nome, matricula, salario_base, linguagem, senioridade): + super()._init_(nome, matricula, salario_base) + self.linguagem = linguagem + self.senioridade = senioridade + + def calcular_bonus(self): + nivel = self.senioridade.lower() + + if nivel == "junior": + return self.salario_base * 0.05 + elif nivel == "pleno": + return self.salario_base * 0.10 + elif nivel == "senior": + return self.salario_base * 0.15 + + return 0.0 + + def calcular_descontos(self): + return self.salario_base * 0.08 + + def calcular_adicionais(self): + ling = self.linguagem.lower() + + if ling == "python": + return self.salario_base + 500.0 + elif ling == "java": + return self.salario_base + 400.0 + elif ling == "javascript": + return self.salario_base + 350.0 + + pass \ No newline at end of file diff --git a/Guia2/src/folha_pagamento/estagiario.py b/Guia2/src/folha_pagamento/estagiario.py new file mode 100644 index 0000000..e10fd20 --- /dev/null +++ b/Guia2/src/folha_pagamento/estagiario.py @@ -0,0 +1,26 @@ +from folha_pagamento.funcionario import Funcionario + +# Desenvolva a classe Estagiario aqui. + +class Estagiario(Funcionario): + def _init_(self, nome, matricula, salario_base, curso, carga_horaria): + super()._init_(nome, matricula, salario_base) + self.curso = curso + self.carga_horaria = carga_horaria + + def calcular_bonus(self): + return self.salario_base * 0.03 + + def calcular_descontos(self): + return self.salario_base * 0.02 + + def calcular_adicionais(self): + + if self.carga_horaria <= 20: + return self.salario_base + 150.0 + elif self.carga_horaria >20 and self.carga_horaria<=30: + return self.salario_base + 250.0 + elif self.carga_horaria > 30: + return self.salario_base + 350.0 + + pass diff --git a/Guia2/src/folha_pagamento/funcionario.py b/Guia2/src/folha_pagamento/funcionario.py new file mode 100644 index 0000000..0f8dccc --- /dev/null +++ b/Guia2/src/folha_pagamento/funcionario.py @@ -0,0 +1,77 @@ +from abc import ABC, abstractmethod + +class Funcionario(ABC): + def __init__(self, nome: str, matricula: str, salario_base: float): + if not nome: + raise ValueError("Nome inválido.") + + if not matricula: + raise ValueError("Matrícula inválida.") + + if salario_base < 0: + raise ValueError("Salário não pode ser negativo.") + + self._nome = nome + self._matricula = matricula + self._salario_base = salario_base + + # --------------------------- + # Propriedades comuns + # --------------------------- + @property + def nome(self) -> str: + return self._nome + + @property + def matricula(self) -> str: + return self._matricula + + @property + def salario_base(self) -> float: + return self._salario_base + + # --------------------------- + # Métodos concretos + # --------------------------- + def dados_basicos(self) -> dict: + return { + "nome": self.nome, + "matricula": self.matricula, + "salario_base": self.salario_base, + "cargo": self.__class__.__name__ + } + + def calcular_salario_liquido(self) -> float: + return ( + self.salario_base + + self.calcular_bonus() + + self.calcular_adicionais() + - self.calcular_descontos() + ) + + def gerar_contracheque(self) -> dict: + return { + "nome": self.nome, + "matricula": self.matricula, + "cargo": self.__class__.__name__, + "salario_base": self.salario_base, + "bonus": self.calcular_bonus(), + "adicionais": self.calcular_adicionais(), + "descontos": self.calcular_descontos(), + "salario_liquido": self.calcular_salario_liquido() + } + + # --------------------------- + # Contrato abstrato + # --------------------------- + @abstractmethod + def calcular_bonus(self) -> float: + pass + + @abstractmethod + def calcular_descontos(self) -> float: + pass + + @abstractmethod + def calcular_adicionais(self) -> float: + pass \ No newline at end of file diff --git a/Guia2/src/folha_pagamento/gerente.py b/Guia2/src/folha_pagamento/gerente.py new file mode 100644 index 0000000..354df49 --- /dev/null +++ b/Guia2/src/folha_pagamento/gerente.py @@ -0,0 +1,34 @@ +from folha_pagamento.funcionario import Funcionario + +# Desenvolva a classe Gerente aqui. + +class Gerente(Funcionario): + def _init_(self, nome, matricula, salario_base, setor, qtd_equipe): + super()._init_(nome, matricula, salario_base) + self.setor = setor + self.qtd_equipe = qtd_equipe + + def calcular_bonus(self): + + if self.setor <= 5: + return self.salario_base * 0.10 + elif self.setor > 5 and self.qtd_equipe <=10: + return self.salario_base * 0.15 + elif self.setor > 10: + return self.salario_base * 0.15 + + return 0.0 + + def calcular_descontos(self): + return self.salario_base * 0.12 + + def calcular_adicionais(self): + + if self.qtd_equipe > 10: + return self.salario_base + 2000.0 + elif self.qtd_equipe > 5: + return self.salario_base + 1000.0 + + return self.salario_base + 500.0 + + pass diff --git a/Guia2/tests/__init__.py b/Guia2/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Guia2/tests/__pycache__/__init__.cpython-312.pyc b/Guia2/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..65afc7b Binary files /dev/null and b/Guia2/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/Guia2/tests/__pycache__/test_desenvolvedor.cpython-312-pytest-8.3.5.pyc b/Guia2/tests/__pycache__/test_desenvolvedor.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000..e7c7fd2 Binary files /dev/null and b/Guia2/tests/__pycache__/test_desenvolvedor.cpython-312-pytest-8.3.5.pyc differ diff --git a/Guia2/tests/__pycache__/test_estagiario.cpython-312-pytest-8.3.5.pyc b/Guia2/tests/__pycache__/test_estagiario.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000..bdcfb1c Binary files /dev/null and b/Guia2/tests/__pycache__/test_estagiario.cpython-312-pytest-8.3.5.pyc differ diff --git a/Guia2/tests/__pycache__/test_gerente.cpython-312-pytest-8.3.5.pyc b/Guia2/tests/__pycache__/test_gerente.cpython-312-pytest-8.3.5.pyc new file mode 100644 index 0000000..42c8ece Binary files /dev/null and b/Guia2/tests/__pycache__/test_gerente.cpython-312-pytest-8.3.5.pyc differ diff --git a/Guia2/tests/integrity.py b/Guia2/tests/integrity.py new file mode 100644 index 0000000..6d3ebd9 --- /dev/null +++ b/Guia2/tests/integrity.py @@ -0,0 +1,32 @@ +from pathlib import Path +import hashlib + + +def gerar_hash_pasta(pasta=".", extensao=".py"): + arquivos = sorted( + [ + arquivo + for arquivo in Path(pasta).rglob(f"*{extensao}") + if arquivo.name != "integrity.py" + ] + ) + + print(arquivos) + + hash_global = hashlib.sha256() + + for arquivo in arquivos: + # inclui nome/caminho do arquivo no hash + hash_global.update(str(arquivo.relative_to(pasta)).encode()) + + # inclui conteúdo + with open(arquivo, "rb") as f: + while bloco := f.read(4096): + hash_global.update(bloco) + + return hash_global.hexdigest() + + +if __name__ == "__main__": + resultado = gerar_hash_pasta() + print(resultado) \ No newline at end of file diff --git a/Guia2/tests/test_desenvolvedor.py b/Guia2/tests/test_desenvolvedor.py new file mode 100644 index 0000000..0ee0137 --- /dev/null +++ b/Guia2/tests/test_desenvolvedor.py @@ -0,0 +1,44 @@ +import pytest +from src import Desenvolvedor + + +@pytest.fixture +def desenvolvedor(): + # Instância base com salário 5000, linguagem Python, senioridade pleno + return Desenvolvedor("Ana", "123", 5000.0, "Python", "pleno") + +# ---- Testes para calcular_bonus() ---- +@pytest.mark.parametrize("senioridade,percentual", [ + ("junior", 0.05), + ("pleno", 0.10), + ("senior", 0.15), +]) +def test_bonus_por_senioridade(desenvolvedor, senioridade, percentual): + desenvolvedor.senioridade = senioridade + esperado = percentual * desenvolvedor.salario_base + assert desenvolvedor.calcular_bonus() == esperado + +# ---- Teste para calcular_descontos() ---- +def test_desconto_fixo(desenvolvedor): + # 8% do salário base + assert desenvolvedor.calcular_descontos() == 0.08 * desenvolvedor.salario_base + +# ---- Testes para calcular_adicionais() ---- +@pytest.mark.parametrize("linguagem,adicional_esperado", [ + ("Python", 500), + ("Java", 400), + ("JavaScript", 350), + ("C++", 200), + ("Ruby", 200), +]) +def test_adicional_por_linguagem(desenvolvedor, linguagem, adicional_esperado): + desenvolvedor.linguagem = linguagem + assert desenvolvedor.calcular_adicionais() == adicional_esperado + +# ---- Teste integrado de salário líquido ---- +def test_salario_liquido_pleno_python(desenvolvedor): + desenvolvedor.senioridade = "pleno" + desenvolvedor.linguagem = "Python" + # salario_base (5000) + bonus(10%) + adicional(500) - desconto(8%) + esperado = 5000 + 500 + 500 - 400 # 5600 + assert desenvolvedor.calcular_salario_liquido() == esperado \ No newline at end of file diff --git a/Guia2/tests/test_estagiario.py b/Guia2/tests/test_estagiario.py new file mode 100644 index 0000000..e1007fe --- /dev/null +++ b/Guia2/tests/test_estagiario.py @@ -0,0 +1,38 @@ +import pytest +from src import Estagiario + + +@pytest.fixture +def estagiario(): + # Salário base 1500, carga horária 30h + return Estagiario("Carlos", "456", 1500.0, "Engenharia", 30) + +# ---- Teste do bônus fixo ---- +def test_bonus_fixo(estagiario): + # 3% do salário base + assert estagiario.calcular_bonus() == 0.03 * estagiario.salario_base + +# ---- Teste do desconto fixo ---- +def test_desconto_fixo(estagiario): + # 2% do salário base + assert estagiario.calcular_descontos() == 0.02 * estagiario.salario_base + +# ---- Testes de adicionais por carga horária ---- +@pytest.mark.parametrize("carga_horaria,adicional_esperado", [ + (20, 150), + (15, 150), # até 20h inclusive + (25, 250), + (30, 250), # até 30h inclusive + (35, 350), + (40, 350), +]) +def test_adicional_por_carga_horaria(estagiario, carga_horaria, adicional_esperado): + estagiario.carga_horaria = carga_horaria + assert estagiario.calcular_adicionais() == adicional_esperado + +# ---- Teste de salário líquido para carga de 30h ---- +def test_salario_liquido_30h(estagiario): + estagiario.carga_horaria = 30 + # 1500 + 45 (bônus) + 250 (adicional) - 30 (desconto) = 1765 + esperado = 1500 + 45 + 250 - 30 + assert estagiario.calcular_salario_liquido() == esperado \ No newline at end of file diff --git a/Guia2/tests/test_gerente.py b/Guia2/tests/test_gerente.py new file mode 100644 index 0000000..da59182 --- /dev/null +++ b/Guia2/tests/test_gerente.py @@ -0,0 +1,47 @@ +import pytest +from src import Gerente + + +@pytest.fixture +def gerente(): + # Salário base 8000, equipe = 5 (para teste básico) + return Gerente("Roberta", "789", 8000.0, "TI", 5) + +# ---- Testes de bônus por tamanho da equipe ---- +@pytest.mark.parametrize("qtd_equipe,percentual_bonus", [ + (3, 0.10), + (5, 0.10), + (6, 0.15), + (10, 0.15), + (11, 0.20), + (20, 0.20), +]) +def test_bonus_por_tamanho_equipe(gerente, qtd_equipe, percentual_bonus): + gerente.qtd_equipe = qtd_equipe + esperado = percentual_bonus * gerente.salario_base + assert gerente.calcular_bonus() == esperado + +# ---- Teste de desconto fixo ---- +def test_desconto_fixo(gerente): + # 12% do salário base + assert gerente.calcular_descontos() == 0.12 * gerente.salario_base + +# ---- Testes de adicional por tamanho da equipe ---- +@pytest.mark.parametrize("qtd_equipe,adicional_esperado", [ + (1, 500), + (5, 500), + (6, 1000), + (10, 1000), + (11, 2000), + (15, 2000), +]) +def test_adicional_por_tamanho_equipe(gerente, qtd_equipe, adicional_esperado): + gerente.qtd_equipe = qtd_equipe + assert gerente.calcular_adicionais() == adicional_esperado + +# ---- Teste integrado para equipe de 8 pessoas ---- +def test_salario_liquido_equipe_8(gerente): + gerente.qtd_equipe = 8 + # bonus 15% = 1200, adicional = 1000, desconto 12% = 960 + esperado = 8000 + 1200 + 1000 - 960 # 9240 + assert gerente.calcular_salario_liquido() == esperado \ No newline at end of file