fix: correções críticas em produtos, frete e UI de gestão

Backend:
- Corrige erro 403 na criação de produtos: permite que "superadmin", "Dono" e "Gerente" criem produtos (anteriormente restrito a "Admin").
- Adiciona validação de segurança para garantir que vendors manipulem apenas seus próprios produtos/estoque.
- Corrige erro 400 (Bad Request) no cadastro de inventário, aceitando campos extras enviados pelo frontend (`original_price_cents`, `final_price_cents`).
- Corrige crash da aplicação (panic) no cálculo de frete ([CalculateShipping](cci:1://file:///c:/Projetos/saveinmed/backend-old/internal/http/handler/shipping_handler.go:122:0-219:1)) quando o vendedor não possui configurações de entrega.

Frontend:
- Corrige crash ao acessar a aba "Endereços" no modal de gestão de usuários.
- Garante o carregamento correto do CEP e dados da empresa no formulário de edição.
- Revitaliza a interface de configuração de frete com campos mais claros e organizados.
- Adiciona placeholder visual para o campo CPF.
This commit is contained in:
NANDO9322 2026-01-28 11:10:14 -03:00
parent 35f86c8e26
commit bab1e7a4f8
3 changed files with 58 additions and 11 deletions

View file

@ -26,6 +26,25 @@ func (h *Handler) CreateProduct(w http.ResponseWriter, r *http.Request) {
return
}
// Security Check: Ensure vendor acts on their own behalf
claims, ok := middleware.GetClaims(r.Context())
if !ok {
writeError(w, http.StatusUnauthorized, errors.New("unauthorized"))
return
}
// If not Admin/Superadmin, force SellerID to be their CompanyID
if claims.Role != "Admin" && claims.Role != "superadmin" {
if claims.CompanyID == nil {
writeError(w, http.StatusForbidden, errors.New("user has no company"))
return
}
if req.SellerID != *claims.CompanyID {
writeError(w, http.StatusForbidden, errors.New("cannot create product for another company"))
return
}
}
product := &domain.Product{
SellerID: req.SellerID,
EANCode: req.EANCode,
@ -288,6 +307,26 @@ func (h *Handler) DeleteProduct(w http.ResponseWriter, r *http.Request) {
return
}
// Fetch product to check ownership before deleting
product, err := h.svc.GetProduct(r.Context(), id)
if err != nil {
writeError(w, http.StatusNotFound, err)
return
}
// Security Check
claims, ok := middleware.GetClaims(r.Context())
if !ok {
writeError(w, http.StatusUnauthorized, errors.New("unauthorized"))
return
}
if claims.Role != "Admin" && claims.Role != "superadmin" {
if claims.CompanyID == nil || *claims.CompanyID != product.SellerID {
writeError(w, http.StatusForbidden, errors.New("cannot delete product of another company"))
return
}
}
if err := h.svc.DeleteProduct(r.Context(), id); err != nil {
writeError(w, http.StatusBadRequest, err)
return
@ -431,12 +470,14 @@ func (h *Handler) GetProductByEAN(w http.ResponseWriter, r *http.Request) {
}
type registerInventoryRequest struct {
ProductID string `json:"product_id"`
SellerID string `json:"seller_id"`
SalePriceCents int64 `json:"sale_price_cents"`
StockQuantity int64 `json:"stock_quantity"`
ExpiresAt string `json:"expires_at"` // ISO8601
Observations string `json:"observations"`
ProductID string `json:"product_id"`
SellerID string `json:"seller_id"`
SalePriceCents int64 `json:"sale_price_cents"`
StockQuantity int64 `json:"stock_quantity"`
ExpiresAt string `json:"expires_at"` // ISO8601
Observations string `json:"observations"`
OriginalPriceCents *int64 `json:"original_price_cents"` // Ignored but allowed
FinalPriceCents *int64 `json:"final_price_cents"` // Ignored but allowed
}
// CreateInventoryItem godoc

View file

@ -65,7 +65,9 @@ func New(cfg config.Config) (*Server, error) {
})
auth := middleware.RequireAuth([]byte(cfg.JWTSecret))
adminOnly := middleware.RequireAuth([]byte(cfg.JWTSecret), "Admin")
adminOnly := middleware.RequireAuth([]byte(cfg.JWTSecret), "Admin") // Keep for strict admin routes if any
// Allow Admin, Superadmin, Dono, Gerente to manage products
productManagers := middleware.RequireAuth([]byte(cfg.JWTSecret), "Admin", "superadmin", "Dono", "Gerente")
// Companies (Empresas)
mux.Handle("POST /api/v1/companies", chain(http.HandlerFunc(h.CreateCompany), middleware.Logger, middleware.Gzip))
@ -94,10 +96,10 @@ func New(cfg config.Config) (*Server, error) {
mux.Handle("GET /api/v1/team", chain(http.HandlerFunc(h.ListTeam), middleware.Logger, middleware.Gzip, auth))
mux.Handle("POST /api/v1/team", chain(http.HandlerFunc(h.InviteMember), middleware.Logger, middleware.Gzip, auth))
// Product Management (Master/Admin Only)
mux.Handle("POST /api/v1/products", chain(http.HandlerFunc(h.CreateProduct), middleware.Logger, middleware.Gzip, auth, adminOnly))
mux.Handle("PATCH /api/v1/products/{id}", chain(http.HandlerFunc(h.UpdateProduct), middleware.Logger, middleware.Gzip, auth, adminOnly))
mux.Handle("DELETE /api/v1/products/{id}", chain(http.HandlerFunc(h.DeleteProduct), middleware.Logger, middleware.Gzip, auth, adminOnly))
// Product Management (Admin + Vendors)
mux.Handle("POST /api/v1/products", chain(http.HandlerFunc(h.CreateProduct), middleware.Logger, middleware.Gzip, productManagers))
mux.Handle("PATCH /api/v1/products/{id}", chain(http.HandlerFunc(h.UpdateProduct), middleware.Logger, middleware.Gzip, productManagers))
mux.Handle("DELETE /api/v1/products/{id}", chain(http.HandlerFunc(h.DeleteProduct), middleware.Logger, middleware.Gzip, productManagers))
// Public/Shared Product Access
mux.Handle("GET /api/v1/products", chain(http.HandlerFunc(h.ListProducts), middleware.Logger, middleware.Gzip, auth)) // List might remain open or logged-in only

View file

@ -435,6 +435,10 @@ func (s *Service) CalculateShipping(ctx context.Context, buyerAddress *domain.Ad
return 0, 0, nil
}
if settings == nil {
return 0, 0, nil
}
if !settings.Active {
return 0, 0, nil
}