import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; import { Product, ShoppingListItem, Order, InventoryItem, Supplier, User, PlatformFee, PLATFORMS, Customer, Transaction, FinancialSummary, AuctionLot, BiddingTender, Sale, SalesChannel, PaymentMethod, OrderStatus } from '../types'; import { searchProducts } from '../services/geminiService'; import { supabase } from '../services/supabase'; import { Session, User as SupabaseUser } from '@supabase/supabase-js'; interface CRMContextType { // Auth State user: User | null; session: Session | null; authLoading: boolean; isAdmin: boolean; // New signIn: (email: string, password: string) => Promise<{ error: any }>; signOut: () => Promise; updateUserRole: (userId: string, role: 'admin' | 'user') => Promise; // New action // Data State products: Product[]; shoppingList: ShoppingListItem[]; orders: Order[]; inventory: InventoryItem[]; suppliers: Supplier[]; users: User[]; customers: Customer[]; transactions: Transaction[]; searchTerm: string; loading: boolean; error: string | null; searchLoading: boolean; // New separate loading state for search searchError: string | null; // New separate error state for search selectedProduct: Product | null; searchType: 'specific' | 'opportunity'; setSearchType: (type: 'specific' | 'opportunity') => void; overheadPercent: number; exchangeRate: number; activeOrderId: string | null; useOverhead: boolean; setUseOverhead: (use: boolean) => void; // Actions setSearchTerm: (term: string) => void; setSelectedProduct: (product: Product | null) => void; handleSearch: (e: React.FormEvent) => Promise; handleOpportunitySearch: (category: string) => Promise; addToShoppingList: (product: Product) => void; removeFromShoppingList: (id: string) => void; updateShoppingItemQuantity: (id: string, delta: number) => void; updateOrderStatus: (orderId: string, newStatus: OrderStatus) => void; saveOrderAsQuotation: () => void; resumeOrder: (orderId: string) => void; deleteOrder: (orderId: string) => Promise; calculateShoppingTotals: () => { totalParaguayBRL: number; totalCostWithOverhead: number; totalUSD: number; totalApproxProfit: number; }; // New Actions addTransaction: (tx: Omit) => Promise; updateTransaction: (id: string, updates: Partial) => Promise; deleteTransaction: (id: string) => Promise; addCustomer: (customer: Omit) => Promise; // Updated return type updateCustomer: (id: string, updates: Partial) => Promise; deleteCustomer: (id: string) => void; getFinancialSummary: () => FinancialSummary; // Sales Management sales: Sale[]; importSales: (channel: SalesChannel) => Promise; updateSale: (id: string, updates: Partial) => void; // Supplier Actions addSupplier: (supplier: Omit) => void; updateSupplier: (id: string, updates: Partial) => void; deleteSupplier: (id: string) => void; registerSale: (items: { id: string, quantity: number, salePrice: number }[], customerId?: string, paymentMethod?: PaymentMethod) => Promise; // Inventory Management addProduct: (product: Omit) => Promise; updateProduct: (id: string, updates: Partial) => Promise; deleteProduct: (id: string) => Promise; // Settings settings: any; // We will define a stronger type later if needed updateSettings: (newSettings: any) => Promise; } const CRMContext = createContext(undefined); export const CRMProvider: React.FC<{ children: ReactNode }> = ({ children }) => { // --- AUTH STATE --- const [user, setUser] = useState(null); const [session, setSession] = useState(null); const [authLoading, setAuthLoading] = useState(true); const [isAdmin, setIsAdmin] = useState(false); // New State // --- APP STATE --- const [searchTerm, setSearchTerm] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [searchLoading, setSearchLoading] = useState(false); const [searchError, setSearchError] = useState(null); const [products, setProducts] = useState([]); const [selectedProduct, setSelectedProduct] = useState(null); const [searchType, setSearchType] = useState<'specific' | 'opportunity'>('specific'); const [shoppingList, setShoppingList] = useState([]); const [activeOrderId, setActiveOrderId] = useState(null); const [useOverhead, setUseOverhead] = useState(true); // SUPABASE DATA const [orders, setOrders] = useState([]); const [inventory, setInventory] = useState([]); const [suppliers, setSuppliers] = useState([]); const [customers, setCustomers] = useState([]); const [transactions, setTransactions] = useState([]); const [sales, setSales] = useState([]); const [settings, setSettings] = useState({ defaultOverhead: 20, defaultExchange: 5.65, companyName: 'Arbitra System' }); // MOCKED DATA const [users, setUsers] = useState([]); const overheadPercent = settings.defaultOverhead || 20; const exchangeRate = settings.defaultExchange || 5.65; // --- AUTH & INITIAL LOAD --- useEffect(() => { const handleAuthSession = async (currentSession: Session | null) => { setSession(currentSession); if (currentSession?.user) { // Fetch Role from profiles table const { data: profile, error: profileError } = await supabase .from('profiles') .select('role') .eq('id', currentSession.user.id) .maybeSingle(); if (profileError) { console.error("Error fetching user profile:", profileError); } const role = profile?.role || 'user'; setIsAdmin(role === 'admin'); setUser({ id: currentSession.user.id, email: currentSession.user.email!, name: currentSession.user.user_metadata?.full_name || currentSession.user.email!, role: role as 'admin' | 'user', status: 'Active', // Default or fetch from profile avatar: currentSession.user.user_metadata?.avatar_url || "https://api.dicebear.com/7.x/avataaars/svg?seed=Felix" }); fetchData(); } else { setUser(null); setIsAdmin(false); // Optional: Clear state on logout setOrders([]); setInventory([]); } setAuthLoading(false); }; // Check active session on mount supabase.auth.getSession().then(({ data: { session } }) => { handleAuthSession(session); }); // Listen for auth changes const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => { handleAuthSession(session); }); return () => subscription.unsubscribe(); }, []); const signIn = async (email: string, password: string) => { const { error } = await supabase.auth.signInWithPassword({ email, password }); return { error }; }; const signOut = async () => { await supabase.auth.signOut(); setUser(null); setIsAdmin(false); }; const updateUserRole = async (userId: string, role: 'admin' | 'user') => { if (!isAdmin) { alert("Você não tem permissão para atualizar funções de usuário."); return; } const { error } = await supabase.from('profiles').update({ role }).eq('id', userId); if (error) { console.error("Error updating role:", error); alert("Erro ao atualizar função do usuário."); } else { alert("Função atualizada com sucesso."); // Update local state immediately setUsers(prev => prev.map(u => u.id === userId ? { ...u, role } : u)); } }; // --- SUPABASE FETCH --- const fetchData = async () => { setLoading(true); try { // Orders const { data: ordersData } = await supabase.from('orders').select('*').order('created_at', { ascending: false }); if (ordersData) { const parsedOrders = ordersData.map(o => ({ id: o.id, date: o.date || o.created_at, items: typeof o.items === 'string' ? JSON.parse(o.items) : (o.items || []), totalUSD: o.total_usd || 0, totalBRL: o.total_brl || 0, totalCostWithOverhead: o.total_cost_with_overhead || 0, estimatedProfit: o.estimated_profit || 0, status: o.status, supplierName: o.supplier_name || 'Desconhecido' })); setOrders(parsedOrders || []); } // Inventory const { data: inventoryData } = await supabase.from('inventory').select('*').order('created_at', { ascending: false }); if (inventoryData) { setInventory(inventoryData.map((i: any) => ({ id: i.id, name: i.name, sku: i.sku, ean: i.ean, quantity: i.quantity, avgCostBRL: i.avg_cost_brl || 0, marketPriceBRL: i.market_price_brl || 0, lastSupplier: i.last_supplier }))); } // Users (Admin Only) - Moved logic inside check if (isAdmin) { const { data: usersData } = await supabase.from('profiles').select('*').order('created_at', { ascending: false }); if (usersData) { setUsers(usersData.map((u: any) => ({ id: u.id, name: u.full_name || u.email, // Fallback if full_name is empty email: u.email, role: u.role, status: u.status, avatar: u.avatar_url || `https://api.dicebear.com/7.x/initials/svg?seed=${u.email}`, lastAccess: u.last_access }))); } } // Settings const { data: settingsData } = await supabase.from('settings').select('*').single(); if (settingsData) { setSettings({ companyName: settingsData.company_name, cnpj: settingsData.cnpj, ie: settingsData.ie, defaultOverhead: settingsData.default_overhead, defaultExchange: settingsData.default_exchange, geminiKey: settingsData.gemini_key, melhorEnvioToken: settingsData.melhor_envio_token, blingToken: settingsData.bling_token, tinyToken: settingsData.tiny_token, certificatePassword: settingsData.certificate_password, nfeSerie: settingsData.nfe_serie, nfeNumber: settingsData.nfe_number, nfeEnvironment: settingsData.nfe_environment, smtpHost: settingsData.smtp_host, smtpPort: settingsData.smtp_port, smtpUser: settingsData.smtp_user, smtpPass: settingsData.smtp_pass, autoSyncSales: settingsData.auto_sync_sales, autoSyncStock: settingsData.auto_sync_stock, sourcingWebhook: settingsData.sourcing_webhook, electricityCostKwh: settingsData.electricity_cost_kwh, }); } } catch (err) { console.error("Error fetching data:", err); } finally { setLoading(false); } }; const updateSettings = async (newSettings: any) => { // Determine if we need to insert or update (assuming single row for settings for now per user or global) // For simplicity, we'll try to update the first row found, or insert if empty. const dbSettings = { company_name: newSettings.companyName, cnpj: newSettings.cnpj, ie: newSettings.ie, default_overhead: newSettings.defaultOverhead, default_exchange: newSettings.defaultExchange, gemini_key: newSettings.geminiKey, melhor_envio_token: newSettings.melhorEnvioToken, bling_token: newSettings.blingToken, tiny_token: newSettings.tinyToken, certificate_password: newSettings.certificatePassword, nfe_serie: newSettings.nfeSerie, nfe_number: newSettings.nfeNumber, nfe_environment: newSettings.nfeEnvironment, smtp_host: newSettings.smtpHost, smtp_port: newSettings.smtpPort, smtp_user: newSettings.smtpUser, smtp_pass: newSettings.smtpPass, auto_sync_sales: newSettings.autoSyncSales, auto_sync_stock: newSettings.autoSyncStock, }; // Check if settings exist const { data } = await supabase.from('settings').select('id').single(); if (data) { // Update await supabase.from('settings').update(dbSettings).eq('id', data.id); } else { // Insert await supabase.from('settings').insert([dbSettings]); } setSettings(newSettings); }; // --- ACTIONS --- const handleSearch = async (e: React.FormEvent) => { e.preventDefault(); if (!searchTerm.trim()) return; setSearchLoading(true); setSearchError(null); setSelectedProduct(null); setProducts([]); // Clear previous results try { const { products: result } = await searchProducts(searchTerm, exchangeRate); setProducts(result); if (result.length === 0) { setSearchError("Não encontramos nenhum produto com esse nome. Tente termos mais gerais (ex: 'iPhone' em vez de 'iPhone 15 Pro Max 256GB Azul')."); } } catch (err: any) { console.error(err); if (err.message && err.message.includes("VITE_GOOGLE_API_KEY")) { setSearchError("Erro de Configuração: API Key do Google não encontrada. Verifique o arquivo .env"); } else if (err.message && (err.message.includes("fetch") || err.message.includes("network"))) { setSearchError("Erro de Conexão: Não foi possível conectar ao servidor. Verifique sua internet."); } else { setSearchError("Ocorreu um erro ao consultar a inteligência artificial. Por favor, tente novamente em alguns instantes."); } } finally { setSearchLoading(false); } }; const handleOpportunitySearch = async (category: string) => { if (!category.trim()) return; setSearchLoading(true); setSearchError(null); setSelectedProduct(null); setProducts([]); // Clear previous results try { const { searchOpportunities } = await import('../services/geminiService'); const { products: result } = await searchOpportunities(category, useOverhead, exchangeRate); setProducts(result); if (result.length === 0) { setSearchError("Nenhuma oportunidade encontrada com >25% de margem nesta categoria no momento. Tente outra categoria."); } } catch (err: any) { console.error(err); if (err.message && err.message.includes("VITE_GOOGLE_API_KEY")) { setSearchError("Erro de Configuração: API Key do Google não encontrada. Verifique o arquivo .env"); } else if (err.message && (err.message.includes("fetch") || err.message.includes("network"))) { setSearchError("Erro de Conexão: Não foi possível conectar ao servidor. Verifique sua internet."); } else { setSearchError("A IA demorou muito para responder ou encontrou um erro. Tente novamente, geralmente funciona na segunda tentativa."); } } finally { setSearchLoading(false); } }; const addToShoppingList = (p: Product) => { setShoppingList(prev => { const existing = prev.find(item => item.name === p.name && item.store === p.store); if (existing) return prev.map(item => item.id === existing.id ? { ...item, quantity: item.quantity + 1 } : item); return [...prev, { id: crypto.randomUUID(), name: p.name, store: p.store, priceUSD: p.priceUSD, priceBRL: p.priceBRL, quantity: 1, marketPriceBRL: p.marketPriceBRL || (p.priceBRL * 1.6) }]; }); }; const removeFromShoppingList = (id: string) => { setShoppingList(prev => prev.filter(i => i.id !== id)); }; const updateShoppingItemQuantity = (id: string, delta: number) => { setShoppingList(prev => prev.map(i => i.id === id ? { ...i, quantity: Math.max(1, i.quantity + delta) } : i)); }; const updateOrderStatus = async (orderId: string, newStatus: OrderStatus) => { const order = orders.find(o => o.id === orderId); if (!order) return; // Database Update await supabase.from('orders').update({ status: newStatus }).eq('id', orderId); // Inventory Logic if Received if (newStatus === 'Received' && order.status !== 'Received') { const newInventoryItems: InventoryItem[] = order.items.map(item => ({ id: crypto.randomUUID(), name: item.name, sku: `SKU-${Math.random().toString(36).substr(2, 4).toUpperCase()}`, quantity: item.quantity, avgCostBRL: item.priceBRL * (1 + overheadPercent / 100), marketPriceBRL: item.marketPriceBRL, lastSupplier: item.store })); await supabase.from('inventory').insert(newInventoryItems.map(i => ({ id: i.id, name: i.name, sku: i.sku, quantity: i.quantity, avg_cost_brl: i.avgCostBRL, market_price_brl: i.marketPriceBRL, last_supplier: i.lastSupplier }))); // Refresh Inventory const { data } = await supabase.from('inventory').select('*').order('created_at', { ascending: false }); if (data) setInventory(data); } // Local Update setOrders(prev => prev.map(o => o.id === orderId ? { ...o, status: newStatus } : o)); }; const deleteOrder = async (orderId: string) => { // Database Delete const { error } = await supabase.from('orders').delete().eq('id', orderId); if (error) { console.error("Error deleting order:", error); alert("Erro ao excluir pedido."); } else { // Local Update setOrders(prev => prev.filter(o => o.id !== orderId)); } }; const calculateShoppingTotals = () => { const totalParaguayBRL = shoppingList.reduce((acc, item) => acc + (item.priceBRL * item.quantity), 0); const overheadMultiplier = useOverhead ? (1 + overheadPercent / 100) : 1; const totalCostWithOverhead = totalParaguayBRL * overheadMultiplier; const totalUSD = shoppingList.reduce((acc, item) => acc + (item.priceUSD * item.quantity), 0); const totalApproxProfit = shoppingList.reduce((acc, item) => { const costWithOverhead = item.priceBRL * overheadMultiplier; const profitPerUnit = item.marketPriceBRL - costWithOverhead; return acc + (profitPerUnit * item.quantity); }, 0); return { totalParaguayBRL, totalCostWithOverhead, totalUSD, totalApproxProfit }; }; const resumeOrder = (orderId: string) => { const order = orders.find(o => o.id === orderId); if (!order) return; setShoppingList(order.items); setActiveOrderId(order.id); }; const saveOrderAsQuotation = async () => { if (shoppingList.length === 0) return; const totals = calculateShoppingTotals(); if (activeOrderId) { // Update const updatePayload = { items: shoppingList, // Helper handles jsonb total_usd: totals.totalUSD, total_brl: totals.totalParaguayBRL, total_cost_with_overhead: totals.totalCostWithOverhead, estimated_profit: totals.totalApproxProfit, supplier_name: shoppingList[0]?.store || 'Múltiplos' }; await supabase.from('orders').update(updatePayload).eq('id', activeOrderId); setOrders(prev => prev.map(o => o.id === activeOrderId ? { ...o, items: shoppingList, totalUSD: totals.totalUSD, totalBRL: totals.totalParaguayBRL, totalCostWithOverhead: totals.totalCostWithOverhead, estimatedProfit: totals.totalApproxProfit, supplierName: updatePayload.supplier_name } : o)); setActiveOrderId(null); } else { // Create const newOrder: Order = { id: crypto.randomUUID(), date: new Date().toISOString(), items: [...shoppingList], totalUSD: totals.totalUSD, totalBRL: totals.totalParaguayBRL, totalCostWithOverhead: totals.totalCostWithOverhead, estimatedProfit: totals.totalApproxProfit, status: 'Pending', supplierName: shoppingList[0]?.store || 'Múltiplos' }; // Map to DB columns const dbOrder = { id: newOrder.id, date: newOrder.date, status: newOrder.status, total_usd: newOrder.totalUSD, total_brl: newOrder.totalBRL, total_cost_with_overhead: newOrder.totalCostWithOverhead, estimated_profit: newOrder.estimatedProfit, items: newOrder.items, supplier_name: newOrder.supplierName }; const { error } = await supabase.from('orders').insert([dbOrder]); if (error) { console.error("Error saving order:", error); alert(`Erro ao salvar pedido: ${error.message} (${error.details || ''})`); return; } setOrders([newOrder, ...orders]); alert("Pedido salvo com sucesso!"); } setShoppingList([]); }; // --- FINANCIAL ACTIONS --- const addTransaction = async (tx: Omit) => { const id = crypto.randomUUID(); const newTx = { ...tx, id, user_id: user?.id, status: tx.status || 'Pending', payment_method: tx.paymentMethod || 'Cash', due_date: tx.dueDate || new Date().toISOString() }; // Map camelCase to snake_case for DB if needed, but Supabase JS client handles it if keys match DB columns // or if we map explicitly. Let's map explicitly to be safe. const dbTx = { id: newTx.id, date: newTx.date, type: newTx.type, category: newTx.category, amount: newTx.amount, description: newTx.description, status: newTx.status, payment_method: newTx.paymentMethod, // DB column: payment_method due_date: newTx.dueDate, // DB column: due_date user_id: newTx.user_id }; const { error } = await supabase.from('transactions').insert([dbTx]); if (error) console.error("Error adding transaction:", error); // We update local state with the camelCase version for UI setTransactions(prev => [{ ...tx, id: newTx.id, status: newTx.status, paymentMethod: newTx.paymentMethod, dueDate: newTx.dueDate }, ...prev]); }; const updateTransaction = async (id: string, updates: Partial) => { // Map updates const dbUpdates: any = { ...updates }; if (updates.paymentMethod) dbUpdates.payment_method = updates.paymentMethod; if (updates.dueDate) dbUpdates.due_date = updates.dueDate; const { error } = await supabase.from('transactions').update(dbUpdates).eq('id', id); if (error) { console.error("Error updating transaction:", error); alert("Erro ao atualizar transação"); } else { setTransactions(prev => prev.map(t => t.id === id ? { ...t, ...updates } : t)); } }; const deleteTransaction = async (id: string) => { await supabase.from('transactions').delete().eq('id', id); setTransactions(prev => prev.filter(t => t.id !== id)); }; const addCustomer = async (customer: Omit): Promise => { const id = crypto.randomUUID(); const newCustomer = { ...customer, id, totalPurchased: 0 }; const dbCustomer = { id: newCustomer.id, name: newCustomer.name, email: newCustomer.email, phone: newCustomer.phone, city: newCustomer.city, status: newCustomer.status, total_purchased: 0, user_id: user?.id }; const { data, error } = await supabase.from('customers').insert([dbCustomer]).select(); if (error) { console.error("Error adding customer:", error); return null; } setCustomers(prev => [...prev, newCustomer]); return newCustomer; // Return the object }; const updateCustomer = async (id: string, updates: Partial) => { // Map updates to snake_case if necessary, or pass mostly as is if Keys match const dbUpdates: any = { ...updates }; if (updates.totalPurchased) dbUpdates.total_purchased = updates.totalPurchased; await supabase.from('customers').update(dbUpdates).eq('id', id); setCustomers(prev => prev.map(c => c.id === id ? { ...c, ...updates } : c)); }; const deleteCustomer = async (id: string) => { await supabase.from('customers').delete().eq('id', id); setCustomers(prev => prev.filter(c => c.id !== id)); }; const getFinancialSummary = () => { const totalIncome = transactions.filter(t => t.type === 'Income').reduce((acc, t) => acc + t.amount, 0); const totalExpense = transactions.filter(t => t.type === 'Expense').reduce((acc, t) => acc + t.amount, 0); return { totalIncome, totalExpense, balance: totalIncome - totalExpense, recentTransactions: transactions.slice(0, 5) }; }; // --- SUPPLIER ACTIONS --- const addSupplier = async (supplier: Omit) => { const id = crypto.randomUUID(); const newSupplier = { ...supplier, id, user_id: user?.id }; await supabase.from('suppliers').insert([newSupplier]); setSuppliers(prev => [...prev, newSupplier]); }; const updateSupplier = async (id: string, updates: Partial) => { await supabase.from('suppliers').update(updates).eq('id', id); setSuppliers(prev => prev.map(s => s.id === id ? { ...s, ...updates } : s)); }; const deleteSupplier = async (id: string) => { await supabase.from('suppliers').delete().eq('id', id); setSuppliers(prev => prev.filter(s => s.id !== id)); }; // PRODUCT ACTIONS const addProduct = async (product: Omit) => { if (!user) { alert("Erro: Usuário não autenticado. O sistema perdeu a conexão ou você não está logado."); return; } console.log("Attempting to add product:", product); const id = crypto.randomUUID(); const newProduct = { ...product, id }; // In a real app, 'products' and 'inventory' might be separate tables. // Here we treat 'inventory' as the master product list for simplicity as per current structure. const { data, error } = await supabase.from('inventory').insert([{ id: newProduct.id, name: newProduct.name, sku: newProduct.sku, ean: newProduct.ean, quantity: newProduct.quantity, avg_cost_brl: newProduct.avgCostBRL, market_price_brl: newProduct.marketPriceBRL, last_supplier: newProduct.lastSupplier, user_id: user?.id }]).select(); if (error) { console.error("Supabase Error adding product:", error); throw error; } console.log("Product added successfully, DB response:", data); setInventory(prev => [newProduct, ...prev]); }; const updateProduct = async (id: string, updates: Partial) => { // Map to snake_case for DB const dbUpdates: any = { ...updates }; if (updates.avgCostBRL) dbUpdates.avg_cost_brl = updates.avgCostBRL; if (updates.marketPriceBRL) dbUpdates.market_price_brl = updates.marketPriceBRL; if (updates.lastSupplier) dbUpdates.last_supplier = updates.lastSupplier; await supabase.from('inventory').update(dbUpdates).eq('id', id); setInventory(prev => prev.map(p => p.id === id ? { ...p, ...updates } : p)); }; const deleteProduct = async (id: string) => { await supabase.from('inventory').delete().eq('id', id); setInventory(prev => prev.filter(p => p.id !== id)); }; // SALES LOGIC & MANAGEMENT const importSales = async (channel: SalesChannel) => { setLoading(true); // MOCK DELAY & DATA await new Promise(r => setTimeout(r, 1500)); const newSales: Sale[] = Array.from({ length: Math.floor(Math.random() * 3) + 2 }).map((_, i) => ({ id: crypto.randomUUID(), date: new Date().toISOString(), customerName: `Cliente ${channel} ${Math.floor(Math.random() * 1000)}`, items: [ { id: 'mock-item', name: 'Produto Importado ' + (i + 1), quantity: 1, salePrice: Math.random() * 200 + 50 } ], total: 0, // Calculated below status: 'Pending' as const, channel: channel, externalId: `MLB-${Math.floor(Math.random() * 1000000)}`, isStockLaunched: false, isFinancialLaunched: false })).map(s => ({ ...s, total: s.items.reduce((acc, i) => acc + (i.quantity * i.salePrice), 0) })); setSales(prev => [...newSales, ...prev]); // Add to top setLoading(false); }; const updateSale = (id: string, updates: Partial) => { setSales(prev => prev.map(s => { if (s.id !== id) return s; const updatedSale = { ...s, ...updates }; // Sincronização Automática (Mock logic linking checkboxes to actual actions) if (updates.isStockLaunched && !s.isStockLaunched) { // Trigger stock deduction logic theoretically console.log("Stock launched for sale", id); } if (updates.isFinancialLaunched && !s.isFinancialLaunched) { addTransaction({ date: new Date().toISOString(), type: 'Income', category: 'Venda Marketplace', amount: updatedSale.total, description: `Venda ${updatedSale.channel} #${updatedSale.externalId || id}`, status: 'Paid', paymentMethod: 'Other' }); } return updatedSale; })); }; const registerSale = async (items: { id: string, quantity: number, salePrice: number }[], customerId?: string, paymentMethod: PaymentMethod = 'Cash') => { // 1. Deduct from Inventory const updatedInventory = inventory.map(invItem => { const soldItem = items.find(i => i.id === invItem.id); if (soldItem) { return { ...invItem, quantity: Math.max(0, invItem.quantity - soldItem.quantity) }; } return invItem; }); setInventory(updatedInventory); // 2. Add Transaction (Income) const totalAmount = items.reduce((acc, item) => acc + (item.quantity * item.salePrice), 0); const cust = customers.find(c => c.id === customerId); const transaction: Omit = { date: new Date().toISOString(), type: 'Income', category: 'Venda de Produtos', amount: totalAmount, description: `Venda balcão - Cliente: ${cust?.name || 'Não Id.'}`, status: 'Paid', paymentMethod: paymentMethod, // Now correctly using the argument dueDate: new Date().toISOString() }; await addTransaction(transaction); // This is good, but Sales.tsx is calling registerSale which handles logic. // ACTUALLY: registerSale needs to accept payment method or we need to update the transaction logic inside it. // Modifying registerSale signature to accept PaymentMethod would be best practice. // 3. Register as a Sale Record const newSale: Sale = { id: crypto.randomUUID(), date: new Date().toISOString(), customerId: customerId, customerName: cust ? cust.name : 'Cliente Balcão', items: items.map(i => { const inv = inventory.find(inv => inv.id === i.id); return { id: i.id, name: inv?.name || 'Item', quantity: i.quantity, salePrice: i.salePrice, costPrice: inv?.avgCostBRL || 0 // Capture cost at moment of sale }; }), total: totalAmount, status: 'Completed', channel: 'Local', isStockLaunched: true, // Auto-launched for POS isFinancialLaunched: true // Auto-launched for POS }; setSales(prev => [newSale, ...prev]); // 4. Update Customer (if exists) - Mocked for now if (customerId) { console.log(`Updated customer ${customerId} total purchased`); } }; const value = { // Auth user, session, authLoading, signIn, signOut, isAdmin, updateUserRole, // Data products, shoppingList, orders, inventory, suppliers, users, searchTerm, loading, error, selectedProduct, overheadPercent, exchangeRate, activeOrderId, searchLoading, searchError, searchType, setSearchType, customers, transactions, setSearchTerm, setSelectedProduct, handleSearch, handleOpportunitySearch, addToShoppingList, removeFromShoppingList, updateShoppingItemQuantity, updateOrderStatus, saveOrderAsQuotation, calculateShoppingTotals, resumeOrder, deleteOrder, addTransaction, deleteTransaction, updateTransaction, addCustomer, updateCustomer, deleteCustomer, getFinancialSummary, addSupplier, updateSupplier, deleteSupplier, registerSale, addProduct, updateProduct, deleteProduct, settings, updateSettings, sales, importSales, updateSale, useOverhead, setUseOverhead }; return {children}; }; export const useCRM = () => { const context = useContext(CRMContext); if (!context) throw new Error('useCRM must be used within a CRMProvider'); return context; };