refactor(erp): integração direta com banco ERP — schema sar

Revoga ADR 0006 (BD-por-workspace separado). O SAR agora conecta ao
banco PostgreSQL do ERP (módulo SIG) e usa o schema `sar` para tudo.

PRISMA
- Remove: Client, Product, Order, OrderItem, OrderStatusHistory,
  RepTarget, RepDiscountLimit, PushSubscription (modelos isolados)
- Adiciona: Pedido, PedidoItem, HistoricoPedido, AlcadaDesconto,
  MetaRepresentante, PushSubscription (mapeados para sar.*)
- IDs: id_cliente/cod_vendedor/id_empresa são INTEGER (ERP)
- situa: Int (1=Pendente 2=Aprovado 3=Cancelado 4=Faturado)
- JWT: workspace_id:string → id_empresa:number
- URL: inclui ?schema=sar para Prisma rotear ao schema ERP

SERVICES
- ClientsService: $queryRawUnsafe contra sar.vw_clientes + sar.pedidos
- CatalogService: $queryRawUnsafe contra sar.vw_produtos + sar.vw_estoque
- OrdersService: Prisma models Pedido/PedidoItem/HistoricoPedido/AlcadaDesconto
- DashboardService: MetaRepresentante + queries raw para inativos
- NotificationsService: PushSubscription com codVendedor + idEmpresa

CONTRATOS (api-interface)
- client.contract: campos ERP (idCliente, nome, cgcpf, cod_vendedor…)
- order.contract: PedidoSummary/PedidoDetail/CreatePedido + SITUA_LABEL
- product.contract: ProdutoSummary/ProdutoDetail (vw_produtos)
- auth.contract: workspaceId:string → idEmpresa:number

WEB
- Todos os cockpits e queries atualizados para os novos tipos

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 21:51:16 +00:00
parent 246eb28bb1
commit b0b60d7a14
39 changed files with 1433 additions and 1544 deletions

View File

@@ -1,10 +1,12 @@
import { Injectable } from '@nestjs/common';
import { ClsService } from 'nestjs-cls';
import { OrderStatus } from '@prisma/client';
import type { SubscribePayload, PendingCountResponse } from '@sar/api-interface';
import type { WorkspaceClsStore } from '../workspace/workspace.types';
import { PushService, type PushPayload } from './push.service';
// Situa: 1=Pendente Aprovação
const SITUA_PENDENTE = 1;
@Injectable()
export class NotificationsService {
constructor(
@@ -15,12 +17,21 @@ export class NotificationsService {
async subscribe(userId: string, role: string, dto: SubscribePayload): Promise<void> {
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 codVendedor = userId ? parseInt(userId, 10) : null;
await prisma.pushSubscription.upsert({
where: { endpoint: dto.endpoint },
update: { userId, role, p256dh: dto.keys.p256dh, auth: dto.keys.auth },
update: {
codVendedor,
idEmpresa,
role,
p256dh: dto.keys.p256dh,
auth: dto.keys.auth,
},
create: {
userId,
codVendedor,
idEmpresa,
role,
endpoint: dto.endpoint,
p256dh: dto.keys.p256dh,
@@ -39,10 +50,11 @@ export class NotificationsService {
async pendingCount(userId: string, role: string): Promise<PendingCountResponse> {
const prisma = this.cls.get('prisma');
if (!prisma) throw new Error('prisma não disponível no CLS');
const idEmpresa = this.cls.get('idEmpresa');
if (role === 'supervisor' || role === 'manager' || role === 'admin') {
const count = await prisma.order.count({
where: { status: OrderStatus.pending_approval, deletedAt: null },
const count = await prisma.pedido.count({
where: { situa: SITUA_PENDENTE, idEmpresa },
});
return { count };
}
@@ -50,24 +62,32 @@ export class NotificationsService {
return { count: 0 };
}
// Envia push para todos os supervisores/managers/admin do workspace.
// Envia push para todos os supervisores/managers/admin da empresa.
async notifySupervisors(payload: PushPayload): Promise<void> {
const prisma = this.cls.get('prisma');
if (!prisma) return;
const idEmpresa = this.cls.get('idEmpresa');
const subs = await prisma.pushSubscription.findMany({
where: { role: { in: ['supervisor', 'manager', 'admin'] } },
where: {
idEmpresa,
role: { in: ['supervisor', 'manager', 'admin'] },
},
});
await Promise.allSettled(subs.map((s) => this.push.send(s, payload)));
}
// Envia push para um userId específico (todos os dispositivos registrados).
// Envia push para um codVendedor específico (todos os dispositivos registrados).
async notifyUser(userId: string, payload: PushPayload): Promise<void> {
const prisma = this.cls.get('prisma');
if (!prisma) return;
const idEmpresa = this.cls.get('idEmpresa');
const codVendedor = parseInt(userId, 10);
const subs = await prisma.pushSubscription.findMany({ where: { userId } });
const subs = await prisma.pushSubscription.findMany({
where: { idEmpresa, codVendedor },
});
await Promise.allSettled(subs.map((s) => this.push.send(s, payload)));
}
}