diff --git a/cmd/cli/main.go b/cmd/cli/main.go new file mode 100644 index 0000000..ccda995 --- /dev/null +++ b/cmd/cli/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "controle-de-estoque/internal/cli" + inmemory "controle-de-estoque/internal/repository/in_memory" + "controle-de-estoque/internal/service" +) + +func main() { + // Aqui é o único lugar que "conhece" todas as camadas. + // Para trocar a persistência (ex: Postgres, arquivo JSON), + // basta trocar esta linha por outra implementação de + // repository.InventoryRepository — nada mais muda. + repo := inmemory.NewInventorytRepository() + + svc := service.NewInventoryService(repo) + + app := cli.NewApp(svc) + app.Run() +} diff --git a/controller/controller.go b/controller/controller.go deleted file mode 100644 index cde9429..0000000 --- a/controller/controller.go +++ /dev/null @@ -1,78 +0,0 @@ -package controller - -import ( - "controle-de-estoque/model" - "controle-de-estoque/view" -) - -type Controller struct { - inventory *model.Inventory -} - -func New(inventory *model.Inventory) *Controller { - return &Controller{inventory: inventory} -} - -// Método principal de entrada da CLI -func (c *Controller) Execute() { - for { - view.ShowMenu() - input, err := view.ReadMenuInput() - - if err != nil { - view.ShowError(err) - break - } - - switch input { - case "1", "adicionar": - c.AddProduct() - case "2", "listar": - c.listAllProducts() - case "3", "atualizar": - c.updateProduct() - case "4", "deletar": - c.deleteProduct() - default: - view.ShowMessage("Sistema encerrado!", nil) - return - } - } -} - -func (c *Controller) AddProduct() { - productInput := view.ReadAddProductInput() - p := c.inventory.AddProduct(productInput) - view.ShowMessage("Produto criado com ID %d: %s\n", []any{p.ID, p.Name}) -} - -func (c *Controller) listAllProducts() { - products := c.inventory.FetchProducts() - view.ShowAllProducts(products) -} - -func (c *Controller) updateProduct() { - productID, field := view.ReadEditProductInput() - - value := view.ReadNewValueInput(field) - - updated, err := c.inventory.UpdateProduct(productID, field, value) - - if err != nil { - view.ShowError(err) - return - } - - view.ShowMessage("Produto %d atualizado com sucesso!\n", []any{updated.ID}) -} - -func (c *Controller) deleteProduct() { - productID := view.ReadDeleteProductInput() - - if err := c.inventory.DeleteProduct(productID); err != nil { - view.ShowError(err) - return - } - - view.ShowMessage("Produto %d deletado com sucesso!", []any{productID}) -} diff --git a/internal/cli/app.go b/internal/cli/app.go new file mode 100644 index 0000000..675d969 --- /dev/null +++ b/internal/cli/app.go @@ -0,0 +1,58 @@ +package cli + +import ( + "bufio" + "controle-de-estoque/internal/service" + "fmt" + "os" + "strings" +) + +// App representa a aplicação de linha de comando. +// Ele faz o papel de "controller" + "router": lê a entrada do usuário, +// decide qual ação executar e chama o service correspondente. +type App struct { + service *service.InventorySevice + reader *bufio.Scanner +} + +func NewApp(s *service.InventorySevice) *App { + return &App{ + service: s, + reader: bufio.NewScanner(os.Stdin), + } +} + +func (a *App) Run() { + for { + ShowMenu() + + if !a.reader.Scan() { + return + } + input := strings.TrimSpace(a.reader.Text()) + + switch strings.ToLower(input) { + case "1", "adicionar": + a.handleAdd() + case "2", "listar": + a.handleList() + case "3", "atualizar": + a.handleUpdate() + case "4", "deletar": + a.handleDelete() + case "0", "sair": + fmt.Println("Saindo...") + return + default: + fmt.Println("Opção inválida, tente novamente.") + } + } +} + +// Helper para pedir e ler uma linha de texto do usuário. +func (a *App) readLine(msg string) string { + fmt.Print(msg) + a.reader.Scan() + return strings.TrimSpace(a.reader.Text()) +} diff --git a/internal/cli/handlers.go b/internal/cli/handlers.go new file mode 100644 index 0000000..31f7939 --- /dev/null +++ b/internal/cli/handlers.go @@ -0,0 +1,93 @@ +package cli + +import ( + "fmt" + "strconv" +) + +func (a *App) handleAdd() { + name := a.readLine("Nome do produto: ") + category := a.readLine("Categoria do produto: ") + priceStr := a.readLine("Preço: ") + + price, err := strconv.ParseFloat(priceStr, 64) + + if err != nil { + fmt.Println("Preço inválido.") + return + } + + qtyStr := a.readLine("Quantidade: ") + qty, err := strconv.Atoi(qtyStr) + + if err != nil { + fmt.Println("Quantidade inválida.") + return + } + + product, err := a.service.CreateProduct(name, category, price, qty) + + if err != nil { + fmt.Println("Erro ao adicionar:", err) + return + } + + fmt.Printf("Produto adicionado: #%d %s\n", product.ID, product.Name) +} + +func (a *App) handleList() { + products := a.service.ListProducts() + + if len(products) == 0 { + fmt.Println("Nenhum produto cadastrado.") + return + } + + for _, p := range products { + fmt.Printf( + "==============================================="+"\n"+ + "ID PRODUTO: %d"+"\n"+ + "NOME: %s"+"\n"+ + "CATEGORIA: %s"+"\n"+ + "PREÇO: %.2f"+"\n"+ + "QUANTIDADE: %d"+"\n"+ + "==============================================="+"\n", + p.ID, p.Name, p.Category, p.Price, p.Quantity) + } +} + +func (a *App) handleUpdate() { + idStr := a.readLine("Informe o Id do produto que deseja atualizar: ") + id, err := strconv.Atoi(idStr) + if err != nil { + fmt.Println("ID inválido.") + return + } + + field := a.readLine("Informe o campo que deseja atualizar (nome, categoria, preco, quantidade): ") + value := a.readLine(fmt.Sprintf("Informe o novo valor para %s: ", field)) + + product, err := a.service.UpdateProduct(id, field, value) + if err != nil { + fmt.Println("Erro ao atualizar:", err) + return + } + + fmt.Printf("Produto %d atualizado com sucesso!\n", product.ID) +} + +func (a *App) handleDelete() { + idStr := a.readLine("Informe o Id do produto que deseja deletar: ") + id, err := strconv.Atoi(idStr) + if err != nil { + fmt.Println("ID inválido.") + return + } + + if err := a.service.DeleteProduct(id); err != nil { + fmt.Println("Erro ao deletar:", err) + return + } + + fmt.Printf("Produto %d deletado com sucesso!\n", id) +} diff --git a/internal/cli/menu.go b/internal/cli/menu.go new file mode 100644 index 0000000..6d5cbc7 --- /dev/null +++ b/internal/cli/menu.go @@ -0,0 +1,16 @@ +package cli + +import "fmt" + +func ShowMenu() { + fmt.Print(` + ============ CONTROLE DE ESTOQUE ============== + ESCOLHA UMA DAS OPÇÕES: + 1 / adicionar - Adiciona o produto no estoque + 2 / listar - Lista todos os produtos do estoque + 3 / atualizar - Atualiza um produto do estoque + 4 / deletar - Deleta um produto do estoque + 0 / sair - Sair do programa + ================================================= + Opção: `) +} diff --git a/internal/model/product.go b/internal/model/product.go new file mode 100644 index 0000000..6991d51 --- /dev/null +++ b/internal/model/product.go @@ -0,0 +1,16 @@ +package model + +type Product struct { + ID int + Name string + Category string + Price float64 + Quantity int +} + +type ProductInput struct { + Name string + Category string + Price float64 + Quantity int +} diff --git a/internal/repository/in_memory/inventory_repository.go b/internal/repository/in_memory/inventory_repository.go new file mode 100644 index 0000000..0b20f3c --- /dev/null +++ b/internal/repository/in_memory/inventory_repository.go @@ -0,0 +1,69 @@ +package inmemory + +import ( + "controle-de-estoque/internal/model" + "errors" +) + +var ErrNotFound = errors.New("Produto não encontrado!") + +type InventoryRepository struct { + products map[int]*model.Product + nextID int +} + +func NewInventorytRepository() *InventoryRepository { + return &InventoryRepository{ + products: make(map[int]*model.Product), + nextID: 1, + } +} + +func (r *InventoryRepository) Create(p *model.ProductInput) *model.Product { + product := model.Product{ + ID: r.nextID, + Name: p.Name, + Category: p.Category, + Price: p.Price, + Quantity: p.Quantity, + } + + r.products[r.nextID] = &product + + r.nextID++ + + return &product +} + +func (r *InventoryRepository) FindByID(id int) (*model.Product, error) { + product, ok := r.products[id] + + if !ok { + return nil, ErrNotFound + } + + return product, nil +} + +func (r *InventoryRepository) FindAll() map[int]*model.Product { + return r.products +} + +func (r *InventoryRepository) Update(p *model.Product) error { + if _, ok := r.products[p.ID]; !ok { + return ErrNotFound + } + + r.products[p.ID] = p + + return nil +} + +func (r *InventoryRepository) Delete(id int) error { + if _, ok := r.products[id]; !ok { + return ErrNotFound + } + + delete(r.products, id) + return nil +} diff --git a/internal/repository/inventory_repository.go b/internal/repository/inventory_repository.go new file mode 100644 index 0000000..ac53b9f --- /dev/null +++ b/internal/repository/inventory_repository.go @@ -0,0 +1,11 @@ +package repository + +import "controle-de-estoque/internal/model" + +type InventoryRepository interface { + Create(p *model.ProductInput) *model.Product + FindByID(id int) (*model.Product, error) + FindAll() map[int]*model.Product + Update(p *model.Product) error + Delete(id int) error +} diff --git a/internal/service/inventory_service.go b/internal/service/inventory_service.go new file mode 100644 index 0000000..32fb007 --- /dev/null +++ b/internal/service/inventory_service.go @@ -0,0 +1,91 @@ +package service + +import ( + "controle-de-estoque/internal/model" + "controle-de-estoque/internal/repository" + "errors" + "strconv" + "strings" +) + +type InventorySevice struct { + repo repository.InventoryRepository +} + +func NewInventoryService(repo repository.InventoryRepository) *InventorySevice { + return &InventorySevice{repo: repo} +} + +func (s *InventorySevice) CreateProduct( + name string, + category string, + price float64, + quantity int) (*model.Product, error) { + if name == "" { + return nil, errors.New("O nome é obrigatório") + } + + if quantity < 0 { + return nil, errors.New("A quantidade não pode ser negativa!") + } + + productInput := &model.ProductInput{ + Name: name, + Category: category, + Price: price, + Quantity: quantity, + } + + product := s.repo.Create(productInput) + + return product, nil +} + +func (s *InventorySevice) ListProducts() map[int]*model.Product { + return s.repo.FindAll() +} + +func (s *InventorySevice) GetProduct(id int) (*model.Product, error) { + return s.repo.FindByID(id) +} + +func (s *InventorySevice) UpdateProduct(id int, field, value string) (*model.Product, error) { + product, err := s.repo.FindByID(id) + + if err != nil { + return nil, err + } + + switch strings.ToLower(field) { + case "nome", "name": + product.Name = value + case "categoria", "category": + product.Category = value + case "preco", "preço", "price": + price, err := strconv.ParseFloat(value, 64) + if err != nil { + return nil, errors.New("Preço inválido") + } + product.Price = price + case "quantidade", "quantity": + quantity, err := strconv.Atoi(value) + if err != nil { + return nil, errors.New("Quantidade inválida") + } + product.Quantity = quantity + default: + return nil, errors.New("Campo inválido") + } + + err = s.repo.Update(product) + + if err != nil { + return nil, err + } + + return product, nil +} + +func (s *InventorySevice) DeleteProduct(id int) error { + return s.repo.Delete(id) +} diff --git a/main.go b/main.go deleted file mode 100644 index 65a9d6a..0000000 --- a/main.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "controle-de-estoque/controller" - "controle-de-estoque/model" -) - -func main() { - estoque := model.NewInventory() - controller := controller.New(estoque) - controller.Execute() -} diff --git a/model/inventory.go b/model/inventory.go deleted file mode 100644 index 79d2ba0..0000000 --- a/model/inventory.go +++ /dev/null @@ -1,98 +0,0 @@ -package model - -import ( - "errors" - "strconv" - "strings" -) - -type ProductID int - -var productID ProductID = 1 - -type Product struct { - ID ProductID - Name string - Category string - Price float64 - Quantity int -} - -type ProductInput struct { - Name string - Category string - Price float64 - Quantity int -} - -type Inventory struct { - products map[ProductID]Product -} - -// função auxiliar para criar a instância do objeto Inventory -func NewInventory() *Inventory { - return &Inventory{products: make(map[ProductID]Product)} -} - -func (e *Inventory) AddProduct(i *ProductInput) *Product { - product := Product{ - ID: productID, - Name: i.Name, - Category: i.Category, - Price: i.Price, - Quantity: i.Quantity, - } - - e.products[productID] = product - - productID += 1 - - return &product -} - -func (e *Inventory) FetchProducts() map[ProductID]Product { - return e.products -} - -func (e *Inventory) UpdateProduct(id ProductID, field, value string) (*Product, error) { - product, ok := e.products[id] - - if !ok { - return nil, errors.New("Produto não encontrado") - } - - switch strings.ToLower(field) { - case "nome", "name": - product.Name = value - case "categoria", "category": - product.Category = value - case "preco", "preço", "price": - price, err := strconv.ParseFloat(value, 64) - if err != nil { - return nil, errors.New("Preço inválido") - } - product.Price = price - case "quantidade", "quantity": - quantity, err := strconv.Atoi(value) - if err != nil { - return nil, errors.New("Quantidade inválida") - } - product.Quantity = quantity - default: - return nil, errors.New("Campo inválido") - } - - e.products[id] = product - - return &product, nil -} - -func (e *Inventory) DeleteProduct(id ProductID) error { - if _, ok := e.products[id]; !ok { - return errors.New("Produto não encontrado") - } - - delete(e.products, id) - - return nil -} diff --git a/view/view.go b/view/view.go deleted file mode 100644 index 3d4ad6e..0000000 --- a/view/view.go +++ /dev/null @@ -1,119 +0,0 @@ -package view - -import ( - "bufio" - "controle-de-estoque/model" - "errors" - "fmt" - "os" - "strconv" - "strings" -) - -var reader = bufio.NewReader(os.Stdin) - -func ShowMenu() { - fmt.Print(` - ============ CONTROLE DE ESTOQUE ============== - ESCOLHA UMA DAS OPÇÕES: - 1 / adicionar - Adiciona o produto no estoque - 2 / listar - Lista todos os produtos do estoque - 3 / atualizar - Atualiza um produto do estoque - 4 / deletar - Deleta um produto do estoque - 0 / sair - Sair do programa - ================================================= - `) -} - -func ShowError(err error) { - fmt.Println("Error: ", err) -} - -func ShowMessage(msg string, args []any) { - if args == nil { - fmt.Println(msg) - return - } - - fmt.Printf(msg, args...) -} - -func ReadMenuInput() (string, error) { - input, err := reader.ReadString('\n') - input = strings.TrimSpace(input) - - if err != nil || input == "" { - return input, errors.New("Opção Inválida!") - } - - return input, nil -} - -func ReadAddProductInput() *model.ProductInput { - fmt.Print("Nome do produto: ") - name, _ := reader.ReadString('\n') - - fmt.Print("Categoria do produto: ") - category, _ := reader.ReadString('\n') - - fmt.Print("Preço: ") - precoStr, _ := reader.ReadString('\n') - price, _ := strconv.ParseFloat(strings.TrimSpace(precoStr), 64) - - fmt.Print("Quantidade: ") - qtyStr, _ := reader.ReadString('\n') - quantity, _ := strconv.Atoi(strings.TrimSpace(qtyStr)) - - return &model.ProductInput{ - Name: strings.TrimSpace(name), - Category: strings.TrimSpace(category), - Price: price, - Quantity: quantity, - } -} - -func ReadEditProductInput() (model.ProductID, string) { - fmt.Print("Informe o Id do produto que deseja atualizar: ") - productID, _ := reader.ReadString('\n') - productIDInt, _ := strconv.Atoi(strings.TrimSpace(productID)) - - fmt.Print("Informe o nome do campo que deseja atualizar (nome, categoria, preco, quantidade): ") - field, _ := reader.ReadString('\n') - field = strings.TrimSpace(field) - - return model.ProductID(productIDInt), field -} -func ReadDeleteProductInput() model.ProductID { - fmt.Print("Informe o Id do produto que deseja deletar: ") - productID, _ := reader.ReadString('\n') - productIDInt, _ := strconv.Atoi(strings.TrimSpace(productID)) - - return model.ProductID(productIDInt) -} - -func ReadNewValueInput(field string) string { - fmt.Printf("Informe o novo valor para %s: ", field) - value, _ := reader.ReadString('\n') - - return strings.TrimSpace(value) -} - -func ShowAllProducts(products map[model.ProductID]model.Product) { - for _, p := range products { - id := p.ID - name := p.Name - category := p.Category - price := p.Price - quantity := p.Quantity - - fmt.Printf( - "==============================================="+"\n"+ - "ID PRODUTO: %d"+"\n"+ - "NOME: %s"+"\n"+ - "CATEGORIA: %s"+"\n"+ - "PRICE: %.2f"+"\n"+ - "QUANTIDADE: %d"+"\n"+ - "==============================================="+"\n", - id, name, category, price, quantity) - } -}