feat(products): catálogos de Tipo/Grupo/Subgrupo/Marca/Unidade com submenu e dropdowns
Menu Produtos ganha submenu (AppShell agora suporta itens aninhados) para cadastrar Tipos/Grupos/Marcas/Unidades; Subgrupo é gerenciado a partir da tela do Grupo (sempre pertence a um). O cadastro de item passa a usar dropdowns desses catálogos em vez de texto livre — exatamente o que o comentário original da migration de products já previa como trabalho futuro. Backend: 5 tabelas de catálogo simples (mesmo padrão dos catálogos fiscais), products ganha as FKs correspondentes com backfill, e endpoints CRUD (sem exclusão física, active=false é o "apagar"). Corrige também um buraco de integridade real encontrado na integração: PATCH /v1/products/:id usava COALESCE para os campos de catálogo opcionais, então não dava para distinguir "não mexer" de "limpar" — trocar o Grupo de um produto sem tocar no Subgrupo deixava o vínculo antigo órfão e inconsistente, sem nenhuma validação pegando isso. Agora a atualização valida o estado efetivo resultante (bloqueia com 400 em vez de corromper o vínculo) e productSubgroupId: null limpa de verdade. Suíte: 95/95 verdes (92→95). pnpm turbo run typecheck build: 28/28. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { agent, bearer, closeTestApp, createTestApp, loginSuperAdmin, type AuthedTokens } from "./setup.js";
|
||||
import { agent, bearer, closeTestApp, createTestApp, loginSuperAdmin, qaName, type AuthedTokens } from "./setup.js";
|
||||
|
||||
/**
|
||||
* `renderPdf` is mocked (real call path through quotes.service.ts still
|
||||
@@ -22,6 +22,8 @@ describe("quotes — anexo de conformidade ANATEL/RGC", () => {
|
||||
let resellerId: string;
|
||||
let stfcProductId: string;
|
||||
let pabxProductId: string;
|
||||
let productTypeId: string;
|
||||
let unitId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await createTestApp();
|
||||
@@ -34,12 +36,27 @@ describe("quotes — anexo de conformidade ANATEL/RGC", () => {
|
||||
.expect(201);
|
||||
resellerId = reseller.body.id;
|
||||
|
||||
const productType = await agent(app)
|
||||
.post("/api/v1/products/types")
|
||||
.set(...bearer(admin.accessToken))
|
||||
.send({ name: qaName("Tipo ANATEL") })
|
||||
.expect(201);
|
||||
productTypeId = productType.body.id;
|
||||
|
||||
const unit = await agent(app)
|
||||
.post("/api/v1/products/units")
|
||||
.set(...bearer(admin.accessToken))
|
||||
.send({ name: qaName("Unidade ANATEL") })
|
||||
.expect(201);
|
||||
unitId = unit.body.id;
|
||||
|
||||
const stfcProduct = await agent(app)
|
||||
.post("/api/v1/products")
|
||||
.set(...bearer(admin.accessToken))
|
||||
.send({
|
||||
name: "qa-test- Linha STFC",
|
||||
productType: "service",
|
||||
productTypeId,
|
||||
unitId,
|
||||
price0: 200,
|
||||
price12: 150,
|
||||
serviceType: "STFC",
|
||||
@@ -63,7 +80,7 @@ describe("quotes — anexo de conformidade ANATEL/RGC", () => {
|
||||
const pabxProduct = await agent(app)
|
||||
.post("/api/v1/products")
|
||||
.set(...bearer(admin.accessToken))
|
||||
.send({ name: "qa-test- Ramal PABX", productType: "service", price0: 50, serviceType: "Serviço PABX" })
|
||||
.send({ name: "qa-test- Ramal PABX", productTypeId, unitId, price0: 50, serviceType: "Serviço PABX" })
|
||||
.expect(201);
|
||||
pabxProductId = pabxProduct.body.id;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user