Backend:
- Refatoração crítica em [DeleteOrder](cci:1://file:///c:/Projetos/saveinmed/backend-old/internal/usecase/usecase.go:46:1-46:53): agora devolve o estoque fisicamente para a tabela `products` antes de deletar o pedido, corrigindo o "vazamento" de estoque em pedidos pendentes/cancelados.
- Novo Handler [UpdateInventoryItem](cci:1://file:///c:/Projetos/saveinmed/backend-old/internal/http/handler/product_handler.go:513:0-573:1): implementada lógica para resolver o ID de Inventário (frontend) para o ID de Produto (backend) e atualizar ambas as tabelas (`products` e `inventory_items`) simultaneamente, garantindo consistência entre a visualização e o checkout.
- Compatibilidade Frontend (DTOs):
- Adicionado suporte aos campos `qtdade_estoque` e `preco_venda` (float) no payload de update.
- Removida a validação estrita de JSON (`DisallowUnknownFields`) para evitar erros 400 em payloads com campos extras.
- Registrada rota alias `PUT /api/v1/produtos-venda/{id}` apontando para o manipulador correto.
- Repositório & Testes:
- Implementação de [GetInventoryItem](cci:1://file:///c:/Projetos/saveinmed/backend-old/internal/usecase/usecase_test.go:189:0-191:1) e [UpdateInventoryItem](cci:1://file:///c:/Projetos/saveinmed/backend-old/internal/http/handler/product_handler.go:513:0-573:1) no PostgresRepo e Interfaces de Serviço.
- Correção de erro de sintaxe (declaração duplicada) em [postgres.go](cci:7://file:///c:/Projetos/saveinmed/backend-old/internal/repository/postgres/postgres.go:0:0-0:0).
- Atualização dos Mocks ([handler_test.go](cci:7://file:///c:/Projetos/saveinmed/backend-old/internal/http/handler/handler_test.go:0:0-0:0), [usecase_test.go](cci:7://file:///c:/Projetos/saveinmed/backend-old/internal/usecase/usecase_test.go:0:0-0:0), [product_service_test.go](cci:7://file:///c:/Projetos/saveinmed/backend-old/internal/usecase/product_service_test.go:0:0-0:0)) para refletir as novas assinaturas de interface e corrigir o build.
Frontend:
- Ajustes de integração nos serviços de carrinho, pedidos e gestão de produtos para suportar o fluxo corrigido.
138 lines
3.8 KiB
Go
138 lines
3.8 KiB
Go
package usecase
|
|
|
|
import (
|
|
"context"
|
|
"encoding/csv"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gofrs/uuid/v5"
|
|
"github.com/saveinmed/backend-go/internal/domain"
|
|
)
|
|
|
|
type ImportReport struct {
|
|
TotalProcessed int `json:"total_processed"`
|
|
SuccessCount int `json:"success_count"`
|
|
FailedCount int `json:"failed_count"`
|
|
Errors []string `json:"errors"`
|
|
}
|
|
|
|
// ImportProducts parses a CSV file and batch inserts valid products.
|
|
// CSV Headers expected: name,ean,price,stock,description
|
|
func (s *Service) ImportProducts(ctx context.Context, sellerID uuid.UUID, r io.Reader) (*ImportReport, error) {
|
|
reader := csv.NewReader(r)
|
|
rows, err := reader.ReadAll()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(rows) < 2 { // Header + at least 1 row
|
|
return nil, errors.New("csv file is empty or missing headers")
|
|
}
|
|
|
|
report := &ImportReport{}
|
|
var products []domain.Product
|
|
|
|
// Header mapping (simple index search)
|
|
headers := rows[0]
|
|
idxMap := make(map[string]int)
|
|
for i, h := range headers {
|
|
idxMap[strings.ToLower(strings.TrimSpace(h))] = i
|
|
}
|
|
|
|
required := []string{"name", "price"}
|
|
for _, req := range required {
|
|
if _, ok := idxMap[req]; !ok {
|
|
return nil, fmt.Errorf("missing required header: %s", req)
|
|
}
|
|
}
|
|
|
|
for i, row := range rows[1:] {
|
|
report.TotalProcessed++
|
|
lineNum := i + 2 // 1-based, +header
|
|
|
|
// Parse Name
|
|
name := strings.TrimSpace(row[idxMap["name"]])
|
|
if name == "" {
|
|
report.FailedCount++
|
|
report.Errors = append(report.Errors, fmt.Sprintf("Line %d: name is required", lineNum))
|
|
continue
|
|
}
|
|
|
|
// Parse Price (float or int string)
|
|
priceStr := strings.TrimSpace(row[idxMap["price"]])
|
|
priceFloat, err := strconv.ParseFloat(priceStr, 64)
|
|
if err != nil {
|
|
report.FailedCount++
|
|
report.Errors = append(report.Errors, fmt.Sprintf("Line %d: invalid price '%s'", lineNum, priceStr))
|
|
continue
|
|
}
|
|
priceCents := int64(priceFloat * 100)
|
|
|
|
// Defaults / Optionals
|
|
// var stock int64 // Removed for Dictionary Mode
|
|
// if idx, ok := idxMap["stock"]; ok && idx < len(row) {
|
|
// if s, err := strconv.ParseInt(strings.TrimSpace(row[idx]), 10, 64); err == nil {
|
|
// stock = s
|
|
// }
|
|
// }
|
|
|
|
var description string
|
|
if idx, ok := idxMap["description"]; ok && idx < len(row) {
|
|
description = strings.TrimSpace(row[idx])
|
|
}
|
|
|
|
var ean string
|
|
if idx, ok := idxMap["ean"]; ok && idx < len(row) {
|
|
ean = strings.TrimSpace(row[idx])
|
|
}
|
|
|
|
prod := domain.Product{
|
|
ID: uuid.Must(uuid.NewV7()),
|
|
SellerID: sellerID,
|
|
Name: name,
|
|
Description: description,
|
|
EANCode: ean,
|
|
PriceCents: priceCents,
|
|
// Stock & ExpiresAt removed from Catalog Dictionary
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
products = append(products, prod)
|
|
}
|
|
|
|
if len(products) > 0 {
|
|
if err := s.repo.BatchCreateProducts(ctx, products); err != nil {
|
|
// If batch fails, we fail mostly everything?
|
|
// Or we could implement line-by-line insert in repo.
|
|
// For ImportProducts, failing the whole batch is acceptable if DB constraint fails.
|
|
return nil, fmt.Errorf("batch insert failed: %w", err)
|
|
}
|
|
}
|
|
|
|
return report, nil
|
|
}
|
|
|
|
func (s *Service) ListCategories(ctx context.Context) ([]string, error) {
|
|
return s.repo.ListCategories(ctx)
|
|
}
|
|
|
|
func (s *Service) GetProductByEAN(ctx context.Context, ean string) (*domain.Product, error) {
|
|
return s.repo.GetProductByEAN(ctx, ean)
|
|
}
|
|
|
|
func (s *Service) RegisterInventoryItem(ctx context.Context, item *domain.InventoryItem) error {
|
|
return s.repo.CreateInventoryItem(ctx, item)
|
|
}
|
|
|
|
func (s *Service) GetInventoryItem(ctx context.Context, id uuid.UUID) (*domain.InventoryItem, error) {
|
|
return s.repo.GetInventoryItem(ctx, id)
|
|
}
|
|
|
|
func (s *Service) UpdateInventoryItem(ctx context.Context, item *domain.InventoryItem) error {
|
|
return s.repo.UpdateInventoryItem(ctx, item)
|
|
}
|