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,128 +1,137 @@
import { z } from 'zod';
// Contratos canônicos de C3 — Consulta de Pedidos.
// Contratos canônicos de C3 — Pedidos SAR.
// Consumidos pela API (output tipado) e pela Web (TanStack Query + parse).
// ADR 0006 revogado: OrderStatus enum → situa Int (1=Pendente, 2=Aprovado, 3=Cancelado, 4=Faturado)
// ─── Enums ────────────────────────────────────────────────────────────────────
// ─── Situa ────────────────────────────────────────────────────────────────────
export const OrderStatusSchema = z.enum([
'budget',
'pending_approval',
'approved',
'invoiced',
'cancelled',
]);
export type OrderStatus = z.infer<typeof OrderStatusSchema>;
// situa: 1=Pendente 2=Aprovado 3=Cancelado 4=Faturado
export const SituaPedidoSchema = z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]);
export type SituaPedido = z.infer<typeof SituaPedidoSchema>;
// ─── OrderItem ────────────────────────────────────────────────────────────────
export const SITUA_LABEL: Record<number, string> = {
1: 'Ag. Aprovação',
2: 'Aprovado',
3: 'Cancelado',
4: 'Faturado',
};
export const OrderItemSchema = z.object({
// ─── PedidoItem ───────────────────────────────────────────────────────────────
export const PedidoItemSchema = z.object({
id: z.string().uuid(),
productCode: z.string(),
productName: z.string(),
quantity: z.string(), // Decimal serializado
unitPrice: z.string(), // Decimal serializado
discountPct: z.string(), // Decimal serializado
subtotal: z.string(), // Decimal serializado
idProduto: z.number().int(),
codProduto: z.string().nullable(),
descProduto: z.string().nullable(),
ordem: z.number().int(),
qtd: z.string(),
precoUnitario: z.string(),
descontoPerc: z.string(),
total: z.string(),
});
export type OrderItem = z.infer<typeof OrderItemSchema>;
export type PedidoItem = z.infer<typeof PedidoItemSchema>;
// ─── OrderStatusHistory ───────────────────────────────────────────────────────
// ─── HistoricoPedido ──────────────────────────────────────────────────────────
export const OrderStatusHistorySchema = z.object({
export const HistoricoPedidoSchema = z.object({
id: z.string().uuid(),
fromStatus: OrderStatusSchema.nullable(),
toStatus: OrderStatusSchema,
changedById: z.string(),
note: z.string().nullable(),
situaAnterior: z.number().int().nullable(),
situaNova: z.number().int(),
changedBy: z.number().int(),
nota: z.string().nullable(),
changedAt: z.iso.datetime(),
});
export type OrderStatusHistory = z.infer<typeof OrderStatusHistorySchema>;
export type HistoricoPedido = z.infer<typeof HistoricoPedidoSchema>;
// ─── Order Summary (lista) ───────────────────────────────────────────────────
// ─── Pedido Summary (lista) ───────────────────────────────────────────────────
export const OrderSummarySchema = z.object({
export const PedidoSummarySchema = z.object({
id: z.string().uuid(),
number: z.string(),
clientId: z.string().uuid(),
clientName: z.string(),
repId: z.string(),
status: OrderStatusSchema,
discountPct: z.string(),
subtotal: z.string(),
numPedSar: z.string(),
idCliente: z.number().int(),
codVendedor: z.number().int(),
situa: z.number().int(),
dtPedido: z.string(),
total: z.string(),
issuedAt: z.iso.datetime(),
approvedAt: z.iso.datetime().nullable(),
invoicedAt: z.iso.datetime().nullable(),
cancelledAt: z.iso.datetime().nullable(),
});
export type OrderSummary = z.infer<typeof OrderSummarySchema>;
// ─── Order Detail ─────────────────────────────────────────────────────────────
export const OrderDetailSchema = OrderSummarySchema.extend({
notes: z.string().nullable(),
approvedById: z.string().nullable(),
idempotencyKey: z.string().nullable(),
descontoPerc: z.string(),
obs: z.string().nullable(),
createdAt: z.iso.datetime(),
updatedAt: z.iso.datetime(),
items: z.array(OrderItemSchema),
history: z.array(OrderStatusHistorySchema),
});
export type OrderDetail = z.infer<typeof OrderDetailSchema>;
export type PedidoSummary = z.infer<typeof PedidoSummarySchema>;
// ─── Pedido Detail ────────────────────────────────────────────────────────────
export const PedidoDetailSchema = PedidoSummarySchema.extend({
totalProdutos: z.string(),
totalIpi: z.string(),
totalIcmsst: z.string(),
descontoValor: z.string(),
acrescimo: z.string(),
comissao: z.string(),
pedFlex: z.string(),
aprovadoPor: z.number().int().nullable(),
aprovadoEm: z.iso.datetime().nullable(),
motivoRecusa: z.string().nullable(),
idempotencyKey: z.string().nullable(),
updatedAt: z.iso.datetime(),
itens: z.array(PedidoItemSchema),
historico: z.array(HistoricoPedidoSchema),
});
export type PedidoDetail = z.infer<typeof PedidoDetailSchema>;
// ─── List query + response ────────────────────────────────────────────────────
export const OrderListQuerySchema = z.object({
clientId: z.string().uuid().optional(),
status: OrderStatusSchema.optional(),
number: z.string().optional(), // busca parcial por número
from: z.iso.datetime().optional(), // issuedAt >= from
to: z.iso.datetime().optional(), // issuedAt <= to
export const PedidoListQuerySchema = z.object({
idCliente: z.coerce.number().int().optional(),
situa: z.coerce.number().int().optional(),
numPedSar: z.string().optional(),
from: z.string().optional(),
to: z.string().optional(),
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().min(1).max(200).default(50),
});
export type OrderListQuery = z.infer<typeof OrderListQuerySchema>;
export type PedidoListQuery = z.infer<typeof PedidoListQuerySchema>;
export const OrderListResponseSchema = z.object({
data: z.array(OrderSummarySchema),
export const PedidoListResponseSchema = z.object({
data: z.array(PedidoSummarySchema),
total: z.number().int().nonnegative(),
page: z.number().int().positive(),
limit: z.number().int().positive(),
});
export type OrderListResponse = z.infer<typeof OrderListResponseSchema>;
export type PedidoListResponse = z.infer<typeof PedidoListResponseSchema>;
// ─── Create Order (POST /orders) ──────────────────────────────────────────────
// ─── Mutações ─────────────────────────────────────────────────────────────────
export const CreateOrderItemSchema = z.object({
productCode: z.string().min(1),
productName: z.string().min(1),
productCategory: z.string().default('geral'),
quantity: z.number().positive(),
unitPrice: z.number().positive(),
discountPct: z.number().min(0).max(100).default(0),
export const CreatePedidoItemSchema = z.object({
idProduto: z.number().int().positive(),
codProduto: z.string().optional(),
descProduto: z.string().min(1),
ordem: z.number().int().min(1),
qtd: z.number().positive(),
precoUnitario: z.number().nonnegative(),
descontoPerc: z.number().min(0).max(100).default(0),
});
export type CreateOrderItem = z.infer<typeof CreateOrderItemSchema>;
export type CreatePedidoItem = z.infer<typeof CreatePedidoItemSchema>;
export const CreateOrderSchema = z.object({
clientId: z.string().uuid(),
discountPct: z.number().min(0).max(100).default(0), // desconto global do pedido
notes: z.string().optional(),
export const CreatePedidoSchema = z.object({
idCliente: z.number().int().positive(),
descontoPerc: z.number().min(0).max(100).default(0),
idPauta: z.number().int().optional(),
codFormapag: z.number().int().optional(),
obs: z.string().optional(),
idempotencyKey: z.string().optional(),
items: z.array(CreateOrderItemSchema).min(1),
itens: z.array(CreatePedidoItemSchema).min(1),
});
export type CreateOrder = z.infer<typeof CreateOrderSchema>;
export type CreatePedido = z.infer<typeof CreatePedidoSchema>;
// ─── Approve / Reject (PATCH /orders/:id/approve|reject) ─────────────────────
export const ApproveOrderSchema = z.object({
// Opcional — supervisor pode ajustar o desconto global. Se omitido, mantém o original.
discountPct: z.number().min(0).max(100).optional(),
note: z.string().optional(),
export const AprovarPedidoSchema = z.object({
descontoPerc: z.number().min(0).max(100).optional(),
nota: z.string().optional(),
});
export type ApproveOrder = z.infer<typeof ApproveOrderSchema>;
export type AprovarPedido = z.infer<typeof AprovarPedidoSchema>;
export const RejectOrderSchema = z.object({
reason: z.string().min(1, 'Motivo é obrigatório'), // FR-5.4
export const RecusarPedidoSchema = z.object({
motivo: z.string().min(1),
});
export type RejectOrder = z.infer<typeof RejectOrderSchema>;
export type RecusarPedido = z.infer<typeof RecusarPedidoSchema>;