Inventory Report

Boas-vindas ao repositório do projeto Inventory Report

Requisitos do projeto

1. Teste o construtor/inicializador do objeto Produto

Crie o teste em: tests/product/test_product.py

Imagem de construtor em Python

Teste se o construtor do objeto Product contém os atributos corretos.

Ao analisar o código do projeto, você encontrará a classe do objeto produto já implementada no arquivo inventory_report/product.py.

Para termos confiança em continuar as implementações, precisamos que você implemente o teste e certifique que o método __init__ da classe Product esteja funcionando corretamente.

O nome deste teste deve ser test_create_product e ele deve verificar o correto preenchimento dos seguintes atributos:

  • id
  • company_name
  • product_name
  • manufacturing_date
  • expiration_date
  • serial_number
  • storage_instructions

O que será testado:

  • 1.1 - Se o teste valida que o atributo id existe na classe e é igual ao passado pelo construtor.
  • 1.2 - Se o teste valida que o atributo company_name existe na classe e é igual ao passado pelo construtor.
  • 1.3 - Se o teste valida que o atributo product_name existe na classe e é igual ao passado pelo construtor.
  • 1.4 - Se o teste valida que o atributo manufacturing_date existe na classe e é igual ao passado pelo construtor.
  • 1.5 - Se o teste valida que o atributo expiration_date existe na classe e é igual ao passado pelo construtor.
  • 1.6 - Se o teste valida que o atributo serial_number existe na classe e é igual ao passado pelo construtor.
  • 1.7 - Se o teste valida que o atributo storage_instructions existe na classe e é igual ao passado pelo construtor.

2. Teste o relatório individual gerado por Produto

Crie o teste em: tests/product_report/test_product_report.py

Teste se o "método mágico" str do objeto Product retorna a frase correta.

Boa notícia! Já implementamos o primeiro relatório no arquivo inventory_report/product.py, e também criamos uma frase com as informações do produto, que será útil para etiquetar o estoque. Para desenvolver esse relatório, utilizamos o método __str__ do Python, que é chamado quando utilizamos a função str(objeto).

Exemplo da frase:

Trecho 1: The product farinha, Trecho 2: with serial number TY68 409C JJ43 ASD1 PL2F, Trecho 3: manufactured in 01-05-2021 Trecho 4: by the company Farinini, Trecho 5: valid until 02-06-2023, Trecho 6: must be stored according to the following instructions: precisa ser armazenado em local protegido da luz.

Agora, para garantirmos uma boa cobertura de testes, precisamos que você implemente o teste. O nome do teste deve ser test_product_report. Ele deve instanciar um objeto Product e verificar se a frase retornada está correta.

O que será testado:

  • 2.1 - Se seu teste verifica se o Trecho 1 do relatório está correto no texto base e no dado inserido nele.
  • 2.2 - Se seu teste verifica se o Trecho 2 do relatório está correto no texto base e no dado inserido nele.
  • 2.3 - Se seu teste verifica se o Trecho 3 do relatório está correto no texto base e no dado inserido nele.
  • 2.4 - Se seu teste verifica se o Trecho 4 do relatório está correto no texto base e no dado inserido nele.
  • 2.5 - Se seu teste verifica se o Trecho 5 do relatório está correto no texto base e no dado inserido nele.
  • 2.6 - Se seu teste verifica se o Trecho 6 do relatório está correto no texto base e no dado inserido nele.

3. Crie a Interface Importer

Crie em: inventory_report/importers.py

Crie a classe abstrata Importer com o inicializador implementado e com o método abstrato import_data.


Como já temos o arquivo com os produtos, precisamos importar os dados. Em razão dos diversos formatos e para não repetir lógica, vamos criar uma classe abstrata que será responsável por definir como as classes importadoras dos dados dos arquivos serão.

Para isso, crie uma classe abstrata chamada Importer, que deve conter um método chamado import_data, que recebe o caminho do arquivo e retorna uma lista de produtos:

O que será testado:

  • 3.1 - Se a classe Importer é abstrata;
  • 3.2 - Se o método __init__ não é abstrato;
  • 3.3 - Se o método __init__ recebe self e path;
  • 3.4 - Se o tipo do path é str;
  • 3.5 - Se o método import_data é abstrato;
  • 3.6 - Se o método import_data recebe self;
  • 3.7 - Se o método import_data retorna uma lista de produtos;

4. Crie a classe JsonImporter

Crie em: inventory_report/importers.py

Crie a classe JsonImporter que herda de Importer e implemente o método import_data para ler um arquivo JSON.


Agora que temos a interface, precisamos criar a classe que irá implementar o método import_data para ler um arquivo JSON. Para isso, crie uma classe chamada JsonImporter, que deve herdar da classe Importer e implementar o método import_data. Esse método, por sua vez, recebe o caminho do arquivo e retorna uma lista de produtos. A lista deve ser retornada como no formato abaixo:

[
  Product(
    id='1',
    product_name='Nicotine Polacrilex',
    company_name='Target Corporation',
    manufacturing_date='2021-02-18',
    expiration_date='2024-09-17',
    serial_number='CR25 1551 4467 2549 4402 1',
    storage_instructions='instrucao 1'
  ),

  Product(
    id='2',
    product_name='fentanyl citrate',
    company_name='Target Corporation',
    manufacturing_date='2020-12-06',
    expiration_date='2024-12-25',
    serial_number='FR29 5951 7573 74OY XKGX 6CSG D20',
    storage_instructions='instrucao 2'
  ),
  // ...
]

O que será testado:

  • 4.1 - Se a classe JsonImporter herda de Importer.
  • 4.2 - Se o método import_data importa corretamente um arquivo JSON válido.
  • 4.3 - Se o método import_data exporta os dados do JSON importado no formato apropriado.

5. Crie a classe Inventory

Crie em: inventory_report/inventory.py

Crie a classe Inventory que armazenará o estoque e poderá adicionar itens a ele.


Com o nosso importador de dados feito, vamos criar a classe que representa um estoque para, a partir dele, gerar o nosso relatório! Atenção para as especificações:

  • A classe Inventory deve poder ser instanciada, de forma opcional, com uma lista de produtos.
  • Caso a lista não seja fornecida, a lista da instância deve ser inicializada como vazia.
  • A classe deve conter um método chamado add_data, que recebe uma lista de produtos e adiciona todos os produtos à lista de produtos da instância.
  • Além disso, a classe deve ter uma propriedade chamada data, que deve ser somente leitura e retornar uma cópia da lista de produtos da instância.

O que será testado:

  • 5.1 - Se o inicializador recebe dois parâmetros: self e data.
  • 5.2 - Se data tem a anotação de tipo List[Products] e é opcional.
  • 5.3 - Se data tem o valor padrão None.
  • 5.4 - Se data é inicializado com uma lista vazia quando o valor padrão é usado.
  • 5.5 - Se data recebe uma lista de produtos.
  • 5.6 - Se data é uma propriedade somente de leitura.
  • 5.7 - Se add_data recebe uma lista de produtos.
  • 5.8 - Se add_data adiciona todos os produtos da lista de produtos recebida por parâmetro à lista de produtos da instância.

6. Crie o protocolo Report

Crie em: inventory_report/reports/report.py

Crie o protocolo Report, que deverá ser usado como contrato dos relatórios simple e complete.


Feita nossa classe de inventário, vamos usá-la para criar nossos relatórios! Visto que teremos dois formatos dele, primeiro vamos criar um contrato para todos os formatos respeitarem. Usaremos um protocolo para isso. Atenção à especificação:

  • No protocolo Report deve haver um método chamado add_inventory recebendo um parâmetro inventory, do tipo Inventory, classe criada no quinto requisito.

  • Deve haver um método chamado generate que retorna uma string.

O que será testado:

  • 6.1 - Se add_inventory recebe dois parâmetros: self e inventory.
  • 6.2 - Se inventory tem a anotação de tipo Inventory.
  • 6.3 - Se generate recebe self.
  • 6.4 - Se generate tem um retorno do tipo str.

7. Crie o relatório SimpleReport

Crie a classe em: inventory_report/reports/simple_report.py

Crie a classe SimpleReport que implementa os métodos add_inventory e generate do protocolo Report.


A classe SimpleReport deve ser inicializada sem parâmetros, contudo, deve ter um atributo para armazenar cada um dos estoques que podem ser adicionados.

O método add_inventory deverá seguir o contrato do protocolo Report e deve ser capaz de adicionar um estoque ao atributo que armazena cada um dos estoques.

O método generate deve ser capaz de gerar o relatório a partir dos produtos que estão presentes em cada um dos estoques armazenados. Atenção às especificações:

  • Ao rodar os testes localmente, você terá um teste para cada validação de cada informação presente no relatório;
  • O método add_inventory deve receber um parâmetro que representa um Inventory, classe implementada no quinto requisito.
  • O método generate deverá retornar uma string de saída com o seguinte formato:
Oldest manufacturing date: YYYY-MM-DD
Closest expiration date: YYYY-MM-DD
Company with the largest inventory: NOME DA EMPRESA
  • A data de validade mais próxima considera somente itens que ainda não venceram.

Dica: O módulo datetime pode te ajudar.

O que será testado:

  • 7.1 - Se o relatório traz corretamente a data de fabricação mais antiga dos estoques,
  • 7.2 - Se o relatório traz corretamente a data de validade mais próxima, descartando itens já vencidos, do estoque
  • 7.3 - Se o relatório traz corretamente a empresa com o maior estoque
  • 7.4 - Se o relatório é gerado no formato especificado.

Bônus

8. Crie a classe CsvImporter

Crie em: inventory_report/importers.py

Crie a classe CsvImporter que herda de Importer e implemente o método import_data para ler um arquivo CSV.


Para não ficarmos limitados a receber estoques em formato JSON, precisamos criar a classe que irá implementar o método import_data para ler um arquivo CSV. Para isso, crie uma classe chamada CsvImporter, que deve herdar da classe Importer e implementar o método import_data, que usa o caminho armazenado em um atributo para retornar uma lista de produtos.

O que será testado:

  • 8.1 - Se a classe CsvImporter herda de Importer.
  • 8.2 - Se o método import_data importa um arquivo CSV.

9. Crie o relatório CompleteReport

Crie em: inventory_report/reports/complete_report.py

Crie a classe CompleteReport que herda de SimpleReport e implementa o método generate do protocolo Report.


O relatório completo deve ser gerado através do método generate escrito dentro da classe CompleteReport e que respeita o contrato criado no protocolo Report.

O método generate deve usar o atributo que armazena as lista de estoques para a estruturação do relatório e deverá retornar uma string formatada como um relatório. Atenção à especificação:

  • A classe CompleteReport deve herdar da classe SimpleReport e sobrescrever o método generate, de modo a especializar seu comportamento.

  • O método deverá retornar uma saída com o seguinte formato:

Oldest manufacturing date: YYYY-MM-DD
Closest expiration date: YYYY-MM-DD
Company with the largest inventory: NOME DA EMPRESA
Stocked products by company:
- Empresa 1: 2
- Empresa 2: 1

O que será testado:

  • 9.1 - Se o relatório simples funciona corretamente, aderente a todas as suas especificações.
  • 9.2 - Se o relatório completo retorna a data de fabricação mais antiga corretamente.
  • 9.3 - Se o relatório completo retorna a data de vencimento mais próxima corretamente, ignorando produtos já vencidos.
  • 9.4 - Se o relatório completo retorna a quantidade correta de produtos estocados por empresa.
  • 9.5 - Se o relatório completo é gerado com o formato especificado.

10. Crie a função process_report_request

Crie em: inventory_report/cli/input_handler.py

Crie a função process_report_request.


Está na hora de ajustar a interface de linha de comando (Command Line Interface, ou CLI) para nossa aplicação que gera relatórios!

No arquivo inventory_report/cli/__init__.py já existe uma CLI implementada com a biblioteca Typer que está configurada para ser chamada da seguinte forma:

ir -p <caminho_da_pasta> -t <tipo_do_relatorio>

A implementação em inventory_report/cli/__init__.py (você não precisa alterar esse arquivo) irá chamar a função process_report_request que você deve implementar no arquivo inventory_report/cli/input_handler.py, com os seguintes argumentos:

  • file_paths: List[str]: Lista de caminhos de arquivos dentro da pasta informada em -p;
  • report_type: str: Tipo de relatório a ser gerado, informado em -t.

A função process_report_request deve retornar um relatório do tipo informado contendo os dados de todos os arquivos listados. Atenção às especificações:

  • A função process_report_request deve receber dois parâmetros: file_paths: List[str] e report_type: str;

  • Deverão ser usadas as classes dos requisitos anteriores para gerar o relatório adequado: Inventory, CsvImporter, JsonImporter, SimpleReport e CompleteReport;

  • Arquivos de extensões não suportadas devem ser ignorados;

  • Caso o tipo de relatório informado não seja suportado, deve ser levantado um ValueError com a mensagem Report type is invalid.;

O que será testado:

  • 10.1 - Se a função gera corretamente relatórios simples quando chamado com um arquivo .json.
  • 10.2 - Se a função gera corretamente relatórios simples quando chamado com um arquivo .csv.
  • 10.3 - Se a função gera corretamente relatórios simples quando chamado com mais de um arquivo.
  • 10.4 - Se a função gera corretamente relatórios completos quando chamado com um arquivo .json.
  • 10.5 - Se a função gera corretamente relatórios completos quando chamado com um arquivo .csv.
  • 10.6 - Se a função gera corretamente relatórios completos quando chamado com mais de um arquivo.
  • 10.7 - Se a função ignora arquivos de extensões não suportadas.
  • 10.8 - Se a função levanta um ValueError quando é passado um tipo de relatório inválido como parâmetro.