feat(c3): consulta de pedidos — schema, api, web (OrdersModule + ClientDetailPage)
- Prisma: Order, OrderItem, OrderStatusHistory + migration - Seed: 17 pedidos em 7 clientes com itens, histórico e desnorm de clientes - @sar/api-interface: contratos Zod (OrderSummary, OrderDetail, OrderListQuery, etc.) - API: GET /orders, GET /orders/:id, GET /clients/:id/orders (últimos 10) - Web: OrdersPage (lista + filtro status/número + pending_approval highlighted) - Web: ClientDetailPage (ficha completa + últimos 10 pedidos) - Web: /pedidos e /pedidos/$id adicionados ao router; ClientDetailPage substitui placeholder Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,49 +23,137 @@ datasource db {
|
||||
// ─── Enums ───────────────────────────────────────────────────────────────────
|
||||
|
||||
// Situação financeira resumida do cliente — cacheável offline (FR-2.4, FR-2.5).
|
||||
// Valor numérico de crédito e inadimplência requerem conexão.
|
||||
enum FinancialStatus {
|
||||
regular
|
||||
attention
|
||||
blocked
|
||||
}
|
||||
|
||||
// Status do pedido (FR-3.2). Transições: budget → pending_approval → approved → invoiced.
|
||||
// Qualquer status pode ir para cancelled.
|
||||
enum OrderStatus {
|
||||
budget // orçamento
|
||||
pending_approval // aprovação pendente
|
||||
approved // aprovado
|
||||
invoiced // faturado
|
||||
cancelled // cancelado
|
||||
}
|
||||
|
||||
// ─── Client (C2) ─────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Cadastro sincronizado do ERP legado (FR-2.6). Rep não cria/edita no MVP.
|
||||
// creditLimit: gerenciado no SAR — admin/supervisor define (OQ-4 resolvido 2026-05-27).
|
||||
// lastOrderAt/lastOrderValue: desnormalizados, atualizados ao sincronizar Orders (C3/C4).
|
||||
// activityStatus: calculado em runtime a partir de lastOrderAt (não persiste — evita drift).
|
||||
// creditLimit: gerenciado no SAR (OQ-4 resolvido 2026-05-27).
|
||||
// lastOrderAt/lastOrderValue/openOrdersCount: desnormalizados de Orders.
|
||||
|
||||
model Client {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
name String // razão social / nome completo
|
||||
tradeName String? // nome fantasia
|
||||
taxId String @unique // CNPJ (14 dígitos) ou CPF (11 dígitos), sem máscara
|
||||
email String?
|
||||
phone String?
|
||||
address Json? // { street, number, complement?, district, city, state, zip }
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
name String
|
||||
tradeName String?
|
||||
taxId String @unique
|
||||
email String?
|
||||
phone String?
|
||||
address Json?
|
||||
|
||||
// Situação financeira — resumo cacheável; detalhes numéricos requerem conexão
|
||||
financialStatus FinancialStatus @default(regular)
|
||||
creditLimit Decimal? @db.Decimal(15, 2)
|
||||
|
||||
// Desnormalizados de Orders (atualizados em C3/C4)
|
||||
repId String // userId do Rep responsável (JWT sub)
|
||||
repId String
|
||||
lastOrderAt DateTime?
|
||||
lastOrderValue Decimal? @db.Decimal(15, 2)
|
||||
openOrdersCount Int @default(0)
|
||||
|
||||
// Controle de sync com ERP
|
||||
erpCode String? // código no ERP legado
|
||||
erpCode String?
|
||||
syncedAt DateTime?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime? // soft delete — não remove fisicamente
|
||||
deletedAt DateTime?
|
||||
|
||||
orders Order[]
|
||||
|
||||
@@index([repId])
|
||||
@@index([taxId])
|
||||
@@index([name])
|
||||
@@index([deletedAt]) // filtragem de soft delete eficiente
|
||||
@@index([deletedAt])
|
||||
}
|
||||
|
||||
// ─── Order (C3) ──────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Pedido emitido pelo Rep. Itens desnormalizados (produto sem FK — C4 traz catálogo).
|
||||
// number: gerado pelo SAR (sequencial por workspace, ex: "PED-00042").
|
||||
// discountPct: desconto global do pedido (além de descontos por item).
|
||||
// approvedById: userId de quem aprovou (se status = approved ou invoiced).
|
||||
|
||||
model Order {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
number String @unique // "PED-00001"
|
||||
clientId String @db.Uuid
|
||||
repId String // userId do Rep que emitiu
|
||||
status OrderStatus @default(budget)
|
||||
discountPct Decimal @default(0) @db.Decimal(5, 2) // % desconto global
|
||||
subtotal Decimal @db.Decimal(15, 2) // soma dos itens sem desconto global
|
||||
total Decimal @db.Decimal(15, 2) // subtotal × (1 - discountPct/100)
|
||||
notes String?
|
||||
approvedById String? // userId de quem aprovou
|
||||
approvedAt DateTime?
|
||||
invoicedAt DateTime?
|
||||
cancelledAt DateTime?
|
||||
|
||||
// Idempotency key para lançamentos offline (C4, FR-4.12)
|
||||
idempotencyKey String? @unique
|
||||
|
||||
issuedAt DateTime @default(now()) // data de emissão pelo Rep
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
|
||||
client Client @relation(fields: [clientId], references: [id])
|
||||
items OrderItem[]
|
||||
history OrderStatusHistory[]
|
||||
|
||||
@@index([clientId])
|
||||
@@index([repId])
|
||||
@@index([status])
|
||||
@@index([issuedAt])
|
||||
@@index([number])
|
||||
@@index([deletedAt])
|
||||
}
|
||||
|
||||
// ─── OrderItem (C3) ──────────────────────────────────────────────────────────
|
||||
//
|
||||
// Item do pedido. Produto desnormalizado (nome/código como string) — catálogo virá em C4.
|
||||
// discountPct: desconto por linha (além do desconto global do Order).
|
||||
|
||||
model OrderItem {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
orderId String @db.Uuid
|
||||
productCode String // código no ERP / catálogo
|
||||
productName String // desnormalizado para exibição offline
|
||||
quantity Decimal @db.Decimal(10, 3)
|
||||
unitPrice Decimal @db.Decimal(15, 2)
|
||||
discountPct Decimal @default(0) @db.Decimal(5, 2)
|
||||
subtotal Decimal @db.Decimal(15, 2) // qty × unitPrice × (1 - discountPct/100)
|
||||
|
||||
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([orderId])
|
||||
}
|
||||
|
||||
// ─── OrderStatusHistory (C3) ─────────────────────────────────────────────────
|
||||
//
|
||||
// Registro imutável de cada transição de status. changedById = userId do ator.
|
||||
|
||||
model OrderStatusHistory {
|
||||
id String @id @default(uuid()) @db.Uuid
|
||||
orderId String @db.Uuid
|
||||
fromStatus OrderStatus?
|
||||
toStatus OrderStatus
|
||||
changedById String // userId
|
||||
note String?
|
||||
changedAt DateTime @default(now())
|
||||
|
||||
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([orderId])
|
||||
@@index([changedAt])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user