import { apiClient } from '@/lib/api/client'; import { buildQuery, type PaginatedResult } from '@/lib/api/types'; import type { ProductService } from '@/lib/api/products'; export type VendaStatus = 'orcamento' | 'confirmada' | 'cancelada' | 'concluida'; export type ParcelaStatus = 'pendente' | 'pago' | 'atrasado' | 'cancelado'; export type FormaPagamento = 'pix' | 'boleto' | 'cartao' | 'dinheiro'; /** Transições válidas (espelha `TRANSICOES_VALIDAS`, `src/modules/sales/sales.service.ts`). */ export const TRANSICOES_VALIDAS: Record = { orcamento: ['confirmada', 'cancelada'], confirmada: ['concluida', 'cancelada'], concluida: [], cancelada: [], }; /** Espelha o model `sale_payments`. */ export interface SalePayment { id: string; sale_id: string; numero_parcela: number; valor: string; vencimento: string; pago_em: string | null; forma_pagamento: string | null; status: ParcelaStatus; alerta_enviado_em: string | null; criado_em: string; } /** Espelha o model `sale_items` + `include: { produto: true }`. */ export interface SaleItem { id: string; sale_id: string; product_service_id: string; quantidade: string; valor_unitario: string; valor_total: string; produto: ProductService; } /** * `SALE_INCLUDE` do backend é usado tanto em `findAll` quanto em * `findOne` — listagem e detalhe têm exatamente o mesmo shape (itens com * produto, parcelas ordenadas, customer resumido). */ export interface SaleListItem { id: string; tenant_id: string; customer_id: string; vendedor_id: string | null; numero_venda: number; status: VendaStatus; valor_total: string; desconto: string; observacoes: string | null; data_venda: string; criado_em: string; atualizado_em: string; customer: { id: string; razao_social: string }; itens: SaleItem[]; parcelas: SalePayment[]; } export interface ListSalesParams { status?: VendaStatus; customer_id?: string; data_inicio?: string; data_fim?: string; page?: number; limit?: number; } export function listSales(params: ListSalesParams) { return apiClient.get>(`/tenant/vendas${buildQuery(params)}`); } export function getSale(id: string) { return apiClient.get(`/tenant/vendas/${id}`); } export interface SaleItemInput { product_service_id: string; quantidade: number; /** Se omitido, usa o `valor_venda` do catálogo. */ valor_unitario?: number; } export interface SaleParcelaInput { valor: number; vencimento: string; } /** * Espelha `CreateSaleDto`/`UpdateSaleDto`. `numero_parcelas` OU `parcelas` * — nunca os dois, nunca nenhum (validado em runtime pelo backend, não * pelo DTO — o schema zod do frontend replica essa regra antes de bater * na API). */ export interface SaleInput { customer_id: string; data_venda?: string; desconto?: number; observacoes?: string; itens: SaleItemInput[]; numero_parcelas?: number; parcelas?: SaleParcelaInput[]; } export function createSale(input: SaleInput & { status?: 'orcamento' | 'confirmada' }) { return apiClient.post('/tenant/vendas', input); } /** Só permitido com a venda em `orcamento` — o backend redefine itens/parcelas por inteiro. */ export function updateSale(id: string, input: SaleInput) { return apiClient.put(`/tenant/vendas/${id}`, input); } /** * Retorna a `sales` "crua" (sem `itens`/`parcelas`/`customer` populados, * diferente de `create`/`update`/`getSale`) — por isso as telas sempre * re-buscam via `getSale` depois de mudar o status, em vez de usar a * resposta direto. */ export function changeSaleStatus(id: string, status: Exclude) { return apiClient.patch<{ id: string; status: VendaStatus }>(`/tenant/vendas/${id}/status`, { status }); }