/** * Client for `apps/api` `/v1/quotes` (see apps/api/src/quotes/quotes.controller.ts, * quotes.service.ts, dto/*, and packages/database/src/repositories/quotes.ts * for the source of truth). * * Unlike Leads/Opportunities, Quotes pass the DB row straight through with * no repository-level mapper — the top-level response is snake_case. The * one exception is `items`, a JSONB column whose element shape * (`QuoteItem`) IS camelCase — a real inconsistency in the API, not a typo * here. Money fields (`implementation_fee`, `impl_geral`, `monthly_total`, * `contract_total`, `proposed_monthly_total`) come back as NUMERIC-typed * strings from Postgres — always `Number(...)` before formatting/math. */ import { apiRequest } from "./http.js"; export const CONTRACT_PERIODS = [0, 12, 24, 36, 48] as const; export type ContractPeriod = (typeof CONTRACT_PERIODS)[number]; export const IMPL_PAYMENT_CONDITIONS = ["À vista", "1+1", "1+2"] as const; export type ImplPaymentCondition = (typeof IMPL_PAYMENT_CONDITIONS)[number]; export type ApprovalStatus = "none" | "pending" | "approved" | "rejected"; export type DealStatus = "orcamento" | "fechado" | "perdido"; export interface QuoteItem { productId: string; productName: string; quantity: number; unitPrice: number; total: number; implUnitPrice: number; implTotal: number; price0: number | null; price12: number | null; price24: number | null; price36: number | null; price48: number | null; } export interface Quote { id: string; created_at: string; updated_at: string; created_by: string | null; quote_number: string; reseller_id: string; client_name: string; client_company: string | null; client_email: string | null; client_phone: string; contract_period: number; fidelity_period: number | null; items: QuoteItem[]; implementation_fee: string; impl_geral: string; monthly_total: string; contract_total: string; proposed_monthly_total: string | null; approval_status: ApprovalStatus; approval_notes: string | null; approved_by_id: string | null; notes: string | null; deal_status: DealStatus; impl_payment_condition: ImplPaymentCondition; client_registration_id: string | null; } export interface QuoteItemInput { productId: string; quantity: number; } export interface CreateQuoteInput { resellerId: string; clientName: string; clientCompany?: string; clientEmail?: string; clientPhone: string; contractPeriod: ContractPeriod; fidelityPeriod?: number; items: QuoteItemInput[]; implGeral?: number; proposedMonthlyTotal?: number; implPaymentCondition?: ImplPaymentCondition; notes?: string; } export interface DecideApprovalInput { decision: "approved" | "rejected"; notes?: string; } export interface CloseDealInput { clientRegistrationId: string; } export function listQuotes(accessToken: string, dealStatus?: string): Promise { const query = dealStatus ? `?dealStatus=${encodeURIComponent(dealStatus)}` : ""; return apiRequest(`/quotes${query}`, { method: "GET" }, { accessToken }); } export function getQuote(id: string, accessToken: string): Promise { return apiRequest(`/quotes/${id}`, { method: "GET" }, { accessToken }); } export function createQuote(input: CreateQuoteInput, accessToken: string): Promise { return apiRequest("/quotes", { method: "POST", body: JSON.stringify(input) }, { accessToken }); } export function decideQuoteApproval( id: string, input: DecideApprovalInput, accessToken: string, ): Promise { return apiRequest( `/quotes/${id}/approval`, { method: "PATCH", body: JSON.stringify(input) }, { accessToken }, ); } export function closeQuoteDeal(id: string, input: CloseDealInput, accessToken: string): Promise { return apiRequest(`/quotes/${id}/close`, { method: "POST", body: JSON.stringify(input) }, { accessToken }); }