feat(api,web): politicas comerciais com multi-selecao de produtos, grupos e subgrupos

- Promocao: produtos com busca remota por codigo/descricao (multi) e
  grupos com busca por nome/codigo (multi) - backend cria uma promocao
  por alvo na mesma transacao ({ids})
- Regra de desconto: grupos e subgrupos multi (subgrupos filtram pelos
  grupos escolhidos) - uma regra por alvo
- Edicao continua por linha, com selects de busca no lugar dos inputs
  de texto livre
- Modais maiores (760px de largura, corpo com altura minima e rolagem)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 20:52:58 +00:00
parent 756e56d8d3
commit 76cd90ae95
4 changed files with 324 additions and 92 deletions

View File

@@ -109,26 +109,41 @@ export class PoliticasService {
};
}
async createPromocao(body: CreatePromocaoBody): Promise<{ id: number }> {
// Cria uma promoção POR ALVO (produto ou grupo). Sem alvo, cria uma linha
// genérica (comportamento anterior preservado).
async createPromocao(body: CreatePromocaoBody): Promise<{ ids: number[] }> {
this.assertManager();
const prisma = this.cls.get('prisma');
if (!prisma) throw new Error('prisma não disponível no CLS');
const idEmpresa = this.cls.get('idEmpresa');
const userId = this.cls.get('userId') ?? 'system';
const p = await prisma.promocao.create({
data: {
idEmpresa,
descricao: body.descricao,
codProduto: body.codProduto ?? null,
grpProd: body.grpProd ?? null,
descPct: body.descPct,
dataInicio: new Date(body.dataInicio),
dataFim: new Date(body.dataFim),
createdBy: userId,
},
const alvos: { codProduto: string | null; grpProd: string | null }[] = [
...(body.codProdutos ?? []).map((c) => ({ codProduto: c, grpProd: null })),
...(body.grpProds ?? []).map((g) => ({ codProduto: null, grpProd: g })),
];
if (alvos.length === 0) alvos.push({ codProduto: null, grpProd: null });
const ids = await prisma.$transaction(async (tx) => {
const created: number[] = [];
for (const alvo of alvos) {
const p = await tx.promocao.create({
data: {
idEmpresa,
descricao: body.descricao,
codProduto: alvo.codProduto,
grpProd: alvo.grpProd,
descPct: body.descPct,
dataInicio: new Date(body.dataInicio),
dataFim: new Date(body.dataFim),
createdBy: userId,
},
});
created.push(p.id);
}
return created;
});
return { id: p.id };
return { ids };
}
async updatePromocao(id: number, body: UpdatePromocaoBody): Promise<void> {
@@ -257,27 +272,42 @@ export class PoliticasService {
};
}
async createRegra(body: CreateRegraDescontoBody): Promise<{ id: number }> {
// Cria uma regra POR ALVO: cada grupo selecionado vira regra por grupo e
// cada subgrupo vira regra por subgrupo. Sem alvos, regra vale para todos.
async createRegra(body: CreateRegraDescontoBody): Promise<{ ids: number[] }> {
this.assertManager();
const prisma = this.cls.get('prisma');
if (!prisma) throw new Error('prisma não disponível no CLS');
const idEmpresa = this.cls.get('idEmpresa');
const userId = this.cls.get('userId') ?? 'system';
const r = await prisma.regraDesconto.create({
data: {
idEmpresa,
descricao: body.descricao,
codGrupo: body.codGrupo ?? null,
codSubgrupo: body.codSubgrupo ?? null,
codVendedor: body.codVendedor ?? null,
descPct: body.descPct,
dataInicio: new Date(body.dataInicio),
dataFim: new Date(body.dataFim),
createdBy: userId,
},
const alvos: { codGrupo: number | null; codSubgrupo: number | null }[] = [
...(body.codGrupos ?? []).map((g) => ({ codGrupo: g, codSubgrupo: null })),
...(body.codSubgrupos ?? []).map((s) => ({ codGrupo: null, codSubgrupo: s })),
];
if (alvos.length === 0) alvos.push({ codGrupo: null, codSubgrupo: null });
const ids = await prisma.$transaction(async (tx) => {
const created: number[] = [];
for (const alvo of alvos) {
const r = await tx.regraDesconto.create({
data: {
idEmpresa,
descricao: body.descricao,
codGrupo: alvo.codGrupo,
codSubgrupo: alvo.codSubgrupo,
codVendedor: body.codVendedor ?? null,
descPct: body.descPct,
dataInicio: new Date(body.dataInicio),
dataFim: new Date(body.dataFim),
createdBy: userId,
},
});
created.push(r.id);
}
return created;
});
return { id: r.id };
return { ids };
}
async updateRegra(id: number, body: UpdateRegraDescontoBody): Promise<void> {