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:
@@ -1,3 +1,4 @@
|
||||
export * from './lib/ping.contract';
|
||||
export * from './lib/auth.contract';
|
||||
export * from './lib/client.contract';
|
||||
export * from './lib/order.contract';
|
||||
|
||||
93
libs/shared/api-interface/src/lib/order.contract.ts
Normal file
93
libs/shared/api-interface/src/lib/order.contract.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
// Contratos canônicos de C3 — Consulta de Pedidos.
|
||||
// Consumidos pela API (output tipado) e pela Web (TanStack Query + parse).
|
||||
|
||||
// ─── Enums ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const OrderStatusSchema = z.enum([
|
||||
'budget',
|
||||
'pending_approval',
|
||||
'approved',
|
||||
'invoiced',
|
||||
'cancelled',
|
||||
]);
|
||||
export type OrderStatus = z.infer<typeof OrderStatusSchema>;
|
||||
|
||||
// ─── OrderItem ────────────────────────────────────────────────────────────────
|
||||
|
||||
export const OrderItemSchema = 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
|
||||
});
|
||||
export type OrderItem = z.infer<typeof OrderItemSchema>;
|
||||
|
||||
// ─── OrderStatusHistory ───────────────────────────────────────────────────────
|
||||
|
||||
export const OrderStatusHistorySchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
fromStatus: OrderStatusSchema.nullable(),
|
||||
toStatus: OrderStatusSchema,
|
||||
changedById: z.string(),
|
||||
note: z.string().nullable(),
|
||||
changedAt: z.iso.datetime(),
|
||||
});
|
||||
export type OrderStatusHistory = z.infer<typeof OrderStatusHistorySchema>;
|
||||
|
||||
// ─── Order Summary (lista) ────────────────────────────────────────────────────
|
||||
|
||||
export const OrderSummarySchema = 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(),
|
||||
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(),
|
||||
createdAt: z.iso.datetime(),
|
||||
updatedAt: z.iso.datetime(),
|
||||
items: z.array(OrderItemSchema),
|
||||
history: z.array(OrderStatusHistorySchema),
|
||||
});
|
||||
export type OrderDetail = z.infer<typeof OrderDetailSchema>;
|
||||
|
||||
// ─── 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
|
||||
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 const OrderListResponseSchema = z.object({
|
||||
data: z.array(OrderSummarySchema),
|
||||
total: z.number().int().nonnegative(),
|
||||
page: z.number().int().positive(),
|
||||
limit: z.number().int().positive(),
|
||||
});
|
||||
export type OrderListResponse = z.infer<typeof OrderListResponseSchema>;
|
||||
Reference in New Issue
Block a user