import React, { useState, useEffect, useCallback } from "react"; import { OrderItem } from "../types"; import { validateCustomerForm, validateAddressForm, validatePaymentForm, validateOrder, } from "../lib/utils"; import { customerService, Customer, CustomerAddress, } from "../src/services/customer.service"; import { lookupService, StoreERP, Billing, PaymentPlan, PartnerSales, } from "../src/services/lookup.service"; import { orderService, CartModel, CartItensModel, DeliveryTaxTable, CalculateDeliveryTaxRequest, } from "../src/services/order.service"; import { authService } from "../src/services/auth.service"; import CreateCustomerDialog from "../components/CreateCustomerDialog"; import CheckoutProductsTable from "../components/checkout/CheckoutProductsTable"; import CheckoutSummary from "../components/checkout/CheckoutSummary"; import CheckoutWizard from "../components/checkout/CheckoutWizard"; import CustomerStep from "../components/checkout/CustomerStep"; import PaymentStep from "../components/checkout/PaymentStep"; import AddressStep from "../components/checkout/AddressStep"; import NotesStep from "../components/checkout/NotesStep"; import CustomerSearchModal from "../components/checkout/CustomerSearchModal"; import InfoModal from "../components/checkout/InfoModal"; import DeliveryTaxModal from "../components/checkout/DeliveryTaxModal"; import DiscountOrderModal from "../components/checkout/DiscountOrderModal"; import LoadingModal from "../components/checkout/LoadingModal"; import ConfirmDialog from "../components/ConfirmDialog"; import { shoppingService } from "../src/services/shopping.service"; import DiscountItemModal from "../components/checkout/DiscountItemModal"; import ProductDetailModal from "../components/ProductDetailModal"; import EditItemModal from "../components/EditItemModal"; import { productService } from "../src/services/product.service"; import { Product } from "../types"; import { shippingService, DeliveryScheduleItem, } from "../src/services/shipping.service"; interface CheckoutViewProps { cart: OrderItem[]; onBack: () => void; onCartUpdate?: () => void; onClearCart?: () => void; } type Step = "customer" | "payment" | "address" | "notes"; const CheckoutView: React.FC = ({ cart, onBack, onCartUpdate, onClearCart, }) => { // Estados dos passos const [currentStep, setCurrentStep] = useState("customer"); // Estados do formulário de cliente const [customerForm, setCustomerForm] = useState({ name: "", document: "", cellPhone: "", cep: "", address: "", number: "", city: "", state: "", complement: "", }); const [customerErrors, setCustomerErrors] = useState>( {} ); const [selectedCustomer, setSelectedCustomer] = useState( null ); const [showCustomerModal, setShowCustomerModal] = useState(false); const [showCreateCustomerModal, setShowCreateCustomerModal] = useState(false); const [customerSearchTerm, setCustomerSearchTerm] = useState(""); const [customerSearchResults, setCustomerSearchResults] = useState< Customer[] >([]); const [isSearchingCustomers, setIsSearchingCustomers] = useState(false); // Estados do formulário de pagamento const [paymentForm, setPaymentForm] = useState({ invoiceStore: null as StoreERP | null, billing: null as Billing | null, paymentPlan: null as PaymentPlan | null, partner: null as PartnerSales | null, }); const [paymentErrors, setPaymentErrors] = useState>( {} ); const [stores, setStores] = useState([]); const [billings, setBillings] = useState([]); const [paymentPlans, setPaymentPlans] = useState([]); const [partners, setPartners] = useState([]); const [isLoadingPaymentData, setIsLoadingPaymentData] = useState(false); // Estados do formulário de endereço const [addressForm, setAddressForm] = useState({ zipCode: "", address: "", number: "", city: "", state: "", complement: "", referencePoint: "", note: "", }); const [addressErrors, setAddressErrors] = useState>( {} ); const [selectedAddress, setSelectedAddress] = useState(null); const [showAddressModal, setShowAddressModal] = useState(false); const [customerAddresses, setCustomerAddresses] = useState( [] ); // Estados do formulário de observações const [notesForm, setNotesForm] = useState({ shippingDate: null as Date | null, scheduleDelivery: false, shippingPriority: "B" as "B" | "M" | "A", notesText1: "", notesText2: "", notesDeliveryText1: "", notesDeliveryText2: "", notesDeliveryText3: "", }); // Estados do resumo do pedido const [taxValue, setTaxValue] = useState("0"); const [discountValue, setDiscountValue] = useState("0,00"); const [deliveryTaxId, setDeliveryTaxId] = useState(null); const [carrierId, setCarrierId] = useState(null); const [isLoadingOrder, setIsLoadingOrder] = useState(false); const [isLoadingPreOrder, setIsLoadingPreOrder] = useState(false); // Estados dos modais de taxa e desconto const [showDeliveryTaxModal, setShowDeliveryTaxModal] = useState(false); const [deliveryTaxOptions, setDeliveryTaxOptions] = useState< DeliveryTaxTable[] >([]); const [isLoadingDeliveryTax, setIsLoadingDeliveryTax] = useState(false); const [showDiscountModal, setShowDiscountModal] = useState(false); const [showCalculatingTaxModal, setShowCalculatingTaxModal] = useState(false); const [isFirstPriorityRender, setIsFirstPriorityRender] = useState(true); // Estados de modais e confirmações const [showInfoModal, setShowInfoModal] = useState(false); const [infoModal, setInfoModal] = useState({ title: "", message: "", description: "", }); const [showConfirmDialog, setShowConfirmDialog] = useState(false); const [confirmDialog, setConfirmDialog] = useState({ type: "confirm" as | "info" | "warning" | "error" | "success" | "delete" | "confirm", title: "", message: "", onConfirm: () => {}, }); const [showTaxErrorDialog, setShowTaxErrorDialog] = useState(false); const [taxErrorDialog, setTaxErrorDialog] = useState({ title: "", message: "", type: "error" as "error" | "info" | "warning" | "success", }); // Estado para dialog de validação de data mínima const [showDateValidationDialog, setShowDateValidationDialog] = useState(false); // Estados para ações dos itens do carrinho const [showDiscountItemModal, setShowDiscountItemModal] = useState(false); const [selectedItemForDiscount, setSelectedItemForDiscount] = useState(null); const [showProductEditModal, setShowProductEditModal] = useState(false); const [selectedItemForEdit, setSelectedItemForEdit] = useState(null); const [selectedItemForRemove, setSelectedItemForRemove] = useState(null); // Cálculos const subtotal = cart.reduce( (acc, item) => acc + item.price * item.quantity, 0 ); const totalWeight = cart.reduce( (acc, item) => acc + (item.weight || 1.0) * item.quantity, 0 ); const taxValueNum = parseFloat(taxValue.replace(",", ".")) || 0; const discountValueNum = parseFloat(discountValue.replace(",", ".").replace(/[^\d,.-]/g, "")) || 0; const total = subtotal + taxValueNum - discountValueNum; // Buscar próximo dia disponível do baldinho ao montar o componente // Usa a mesma lógica do componente Baldinho.tsx useEffect(() => { const loadNextAvailableDeliveryDate = async () => { console.log("📅 [CHECKOUT] ========================================"); console.log("📅 [CHECKOUT] Iniciando busca do próximo dia disponível"); // Só buscar se não houver data já salva no localStorage const savedDataDelivery = localStorage.getItem("dataDelivery"); if (savedDataDelivery) { try { const dataDelivery = JSON.parse(savedDataDelivery); if (dataDelivery.dateDelivery) { const parsedDate = new Date(dataDelivery.dateDelivery); if (!isNaN(parsedDate.getTime())) { console.log( "📅 [CHECKOUT] Data de entrega já existe no localStorage:", parsedDate.toLocaleDateString("pt-BR") ); console.log("📅 [CHECKOUT] Pulando busca na API"); console.log( "📅 [CHECKOUT] ========================================" ); return; // Já tem data salva, não precisa buscar } } } catch (e) { console.error( "📅 [CHECKOUT] Erro ao verificar dataDelivery salva:", e ); } } try { console.log("📅 [CHECKOUT] Chamando API do baldinho..."); console.log( "📅 [CHECKOUT] Endpoint: shippingService.getScheduleDelivery()" ); const response = await shippingService.getScheduleDelivery(); console.log("📅 [CHECKOUT] Resposta da API recebida:", { hasResponse: !!response, hasDeliveries: !!(response && response.deliveries), isArray: Array.isArray(response?.deliveries), deliveriesCount: response?.deliveries?.length || 0, }); if ( response && response.deliveries && Array.isArray(response.deliveries) ) { console.log( "📅 [CHECKOUT] Total de dias recebidos da API:", response.deliveries.length ); // Data de hoje para comparação const today = new Date(); today.setHours(0, 0, 0, 0); console.log( "📅 [CHECKOUT] Data de hoje:", today.toLocaleDateString("pt-BR") ); // Filtrar dias que atendem às condições: // 1. delivery === "S" (entrega disponível) // 2. avaliableDelivery < deliverySize (ainda tem capacidade) // 3. Data >= hoje (no futuro) const availableDays = response.deliveries .filter((item: DeliveryScheduleItem) => { // Converter dateDelivery (ISO string) para Date const itemDate = new Date(item.dateDelivery); // Criar data local a partir dos componentes UTC (igual ao Baldinho) const localDate = new Date( itemDate.getUTCFullYear(), itemDate.getUTCMonth(), itemDate.getUTCDate() ); localDate.setHours(0, 0, 0, 0); // Verificar condições const isDeliveryAvailable = item.delivery === "S"; // IMPORTANTE: avaliableDelivery deve ser MENOR que deliverySize const hasCapacity = item.avaliableDelivery < item.deliverySize; // Data deve ser >= hoje (incluindo hoje) const isFuture = localDate >= today; // Log detalhado para cada dia console.log("📅 [CHECKOUT] Verificando dia:", { dateISO: item.dateDelivery, dateLocal: localDate.toISOString(), dateFormatted: localDate.toLocaleDateString("pt-BR"), delivery: item.delivery, deliverySize: item.deliverySize, avaliableDelivery: item.avaliableDelivery, saleWeigth: item.saleWeigth, conditions: { isDeliveryAvailable: `${item.delivery} === "S" = ${isDeliveryAvailable}`, hasCapacity: `${item.avaliableDelivery} < ${item.deliverySize} = ${hasCapacity}`, isFuture: `${localDate.toLocaleDateString( "pt-BR" )} >= ${today.toLocaleDateString("pt-BR")} = ${isFuture}`, }, passesAllConditions: isDeliveryAvailable && hasCapacity && isFuture, }); return isDeliveryAvailable && hasCapacity && isFuture; }) .map((item: DeliveryScheduleItem) => { const itemDate = new Date(item.dateDelivery); const localDate = new Date( itemDate.getUTCFullYear(), itemDate.getUTCMonth(), itemDate.getUTCDate() ); localDate.setHours(0, 0, 0, 0); // Formatar data como DD/MM/YYYY const formattedDate = localDate.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", }); return { dateString: formattedDate, // DD/MM/YYYY date: localDate, // Date object para comparação delivery: item.delivery, deliverySize: item.deliverySize, avaliableDelivery: item.avaliableDelivery, saleWeigth: item.saleWeigth, rawItem: item, }; }) .sort((a, b) => a.date.getTime() - b.date.getTime()); // Ordenar por data (mais próximo primeiro) console.log("📅 [CHECKOUT] ========================================"); console.log("📅 [CHECKOUT] RESUMO DA BUSCA:"); console.log( "📅 [CHECKOUT] Total de dias na API:", response.deliveries.length ); console.log( "📅 [CHECKOUT] Dias que atendem às condições (delivery='S' e avaliableDelivery < deliverySize e data >= hoje):", availableDays.length ); if (availableDays.length > 0) { // Listar todos os dias disponíveis encontrados console.log("📅 [CHECKOUT] Dias disponíveis encontrados:"); availableDays.forEach((day, index) => { console.log( `📅 [CHECKOUT] ${index + 1}. ${ day.dateString } - DeliverySize: ${day.deliverySize}, AvaliableDelivery: ${ day.avaliableDelivery }, Capacidade: ${ day.avaliableDelivery < day.deliverySize ? "OK" : "ESGOTADO" }` ); }); // Já está ordenado por data (mais próximo primeiro) const nextAvailableDay = availableDays[0]; console.log( "📅 [CHECKOUT] ========================================" ); console.log("📅 [CHECKOUT] PRÓXIMO DIA DISPONÍVEL ENCONTRADO:"); console.log("📅 [CHECKOUT] Data:", nextAvailableDay.dateString); console.log( "📅 [CHECKOUT] Data (Date object):", nextAvailableDay.date.toLocaleDateString("pt-BR") ); console.log("📅 [CHECKOUT] Delivery:", nextAvailableDay.delivery); console.log( "📅 [CHECKOUT] DeliverySize:", nextAvailableDay.deliverySize ); console.log( "📅 [CHECKOUT] AvaliableDelivery:", nextAvailableDay.avaliableDelivery ); console.log( "📅 [CHECKOUT] Capacidade disponível:", nextAvailableDay.avaliableDelivery, "<", nextAvailableDay.deliverySize ); console.log( "📅 [CHECKOUT] ========================================" ); // Setar a data no formulário setNotesForm((prev) => { console.log("📅 [CHECKOUT] Setando data no notesForm..."); console.log( "📅 [CHECKOUT] Data anterior:", prev.shippingDate?.toLocaleDateString("pt-BR") || "null" ); console.log( "📅 [CHECKOUT] Nova data:", nextAvailableDay.date.toLocaleDateString("pt-BR") ); return { ...prev, shippingDate: nextAvailableDay.date, }; }); // Salvar no localStorage const dataDelivery = { dateDelivery: nextAvailableDay.date.toISOString(), scheduleDelivery: notesForm.scheduleDelivery, priorityDelivery: notesForm.shippingPriority, notification: notesForm.notesText1, notification1: notesForm.notesText2, notificationDelivery1: notesForm.notesDeliveryText1, notificationDelivery2: notesForm.notesDeliveryText2, notificationDelivery3: notesForm.notesDeliveryText3, }; localStorage.setItem("dataDelivery", JSON.stringify(dataDelivery)); console.log("📅 [CHECKOUT] Data salva no localStorage:", { dateDelivery: dataDelivery.dateDelivery, dateFormatted: nextAvailableDay.date.toLocaleDateString("pt-BR"), }); console.log( "📅 [CHECKOUT] ========================================" ); } else { console.warn( "📅 [CHECKOUT] ========================================" ); console.warn("📅 [CHECKOUT] NENHUM DIA DISPONÍVEL ENCONTRADO!"); console.warn( "📅 [CHECKOUT] Total de dias na API:", response.deliveries.length ); console.warn( "📅 [CHECKOUT] Dias com delivery='S':", response.deliveries.filter( (d: DeliveryScheduleItem) => d.delivery === "S" ).length ); console.warn( "📅 [CHECKOUT] Dias com capacidade (avaliableDelivery < deliverySize):", response.deliveries.filter( (d: DeliveryScheduleItem) => d.delivery === "S" && d.avaliableDelivery < d.deliverySize ).length ); console.warn("📅 [CHECKOUT] Usando fallback D+3..."); // Se não encontrou nenhum dia disponível, usar D+3 como fallback const today = new Date(); today.setHours(0, 0, 0, 0); const fallbackDate = new Date(today); fallbackDate.setDate(today.getDate() + 3); fallbackDate.setHours(0, 0, 0, 0); console.log( "📅 [CHECKOUT] Data fallback (D+3):", fallbackDate.toLocaleDateString("pt-BR") ); setNotesForm((prev) => ({ ...prev, shippingDate: fallbackDate, })); const dataDelivery = { dateDelivery: fallbackDate.toISOString(), scheduleDelivery: notesForm.scheduleDelivery, priorityDelivery: notesForm.shippingPriority, notification: notesForm.notesText1, notification1: notesForm.notesText2, notificationDelivery1: notesForm.notesDeliveryText1, notificationDelivery2: notesForm.notesDeliveryText2, notificationDelivery3: notesForm.notesDeliveryText3, }; localStorage.setItem("dataDelivery", JSON.stringify(dataDelivery)); console.log( "📅 [CHECKOUT] ========================================" ); } } else { console.error( "📅 [CHECKOUT] ========================================" ); console.error("📅 [CHECKOUT] Resposta da API inválida!"); console.error("📅 [CHECKOUT] Response:", response); console.error( "📅 [CHECKOUT] ========================================" ); } } catch (error) { console.error("📅 [CHECKOUT] ========================================"); console.error("📅 [CHECKOUT] ERRO ao buscar próximo dia disponível:"); console.error("📅 [CHECKOUT] Error:", error); console.error("📅 [CHECKOUT] Stack:", (error as any)?.stack); console.error("📅 [CHECKOUT] Usando fallback D+3 devido a erro..."); // Em caso de erro, usar D+3 como fallback const today = new Date(); today.setHours(0, 0, 0, 0); const fallbackDate = new Date(today); fallbackDate.setDate(today.getDate() + 3); fallbackDate.setHours(0, 0, 0, 0); console.log( "📅 [CHECKOUT] Data fallback (D+3):", fallbackDate.toLocaleDateString("pt-BR") ); setNotesForm((prev) => ({ ...prev, shippingDate: fallbackDate, })); const dataDelivery = { dateDelivery: fallbackDate.toISOString(), scheduleDelivery: notesForm.scheduleDelivery, priorityDelivery: notesForm.shippingPriority, notification: notesForm.notesText1, notification1: notesForm.notesText2, notificationDelivery1: notesForm.notesDeliveryText1, notificationDelivery2: notesForm.notesDeliveryText2, notificationDelivery3: notesForm.notesDeliveryText3, }; localStorage.setItem("dataDelivery", JSON.stringify(dataDelivery)); console.log("📅 [CHECKOUT] ========================================"); } }; loadNextAvailableDeliveryDate(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Executar apenas uma vez ao montar // Carregar dados salvos do localStorage useEffect(() => { const loadSavedData = () => { // Carregar cliente const savedCustomer = localStorage.getItem("customer"); if (savedCustomer) { try { const customer = JSON.parse(savedCustomer) as Customer; setSelectedCustomer(customer); setCustomerForm({ name: customer.name || "", document: customer.cpfCnpj || customer.document || "", cellPhone: customer.cellPhone || customer.phone || "", cep: customer.zipCode || customer.cep || "", address: customer.address || "", number: customer.addressNumber || customer.number || "", city: customer.city || "", state: customer.state || "", complement: customer.complement || "", }); } catch (e) { console.error("Erro ao carregar cliente:", e); } } // Carregar endereço const savedAddress = localStorage.getItem("address"); if (savedAddress) { try { const address = JSON.parse(savedAddress) as CustomerAddress; setSelectedAddress(address); setAddressForm({ zipCode: address.zipCode || "", address: address.address || "", number: address.number || "", city: address.city || "", state: address.state || "", complement: address.complement || "", referencePoint: address.referencePoint || "", note: address.note || "", }); } catch (e) { console.error("Erro ao carregar endereço:", e); } } // Carregar pagamento const savedInvoiceStore = localStorage.getItem("invoiceStore"); if (savedInvoiceStore) { try { setPaymentForm((prev) => ({ ...prev, invoiceStore: JSON.parse(savedInvoiceStore) as StoreERP, })); } catch (e) { console.error("Erro ao carregar filial:", e); } } const savedBilling = localStorage.getItem("billing"); if (savedBilling) { try { setPaymentForm((prev) => ({ ...prev, billing: JSON.parse(savedBilling) as Billing, })); } catch (e) { console.error("Erro ao carregar cobrança:", e); } } const savedPaymentPlan = localStorage.getItem("paymentPlan"); if (savedPaymentPlan) { try { setPaymentForm((prev) => ({ ...prev, paymentPlan: JSON.parse(savedPaymentPlan) as PaymentPlan, })); } catch (e) { console.error("Erro ao carregar plano de pagamento:", e); } } const savedPartner = localStorage.getItem("partner"); if (savedPartner) { try { setPaymentForm((prev) => ({ ...prev, partner: JSON.parse(savedPartner) as PartnerSales, })); } catch (e) { console.error("Erro ao carregar parceiro:", e); } } // Carregar observações const savedDataDelivery = localStorage.getItem("dataDelivery"); if (savedDataDelivery) { try { const dataDelivery = JSON.parse(savedDataDelivery); console.log( "📅 [CHECKOUT] Dados de entrega carregados do localStorage:", dataDelivery ); // Converter dateDelivery para Date, tratando diferentes formatos let shippingDate: Date | null = null; if (dataDelivery.dateDelivery) { if (typeof dataDelivery.dateDelivery === "string") { // Se for string ISO, converter para Date const parsedDate = new Date(dataDelivery.dateDelivery); // Verificar se a data é válida if (!isNaN(parsedDate.getTime())) { shippingDate = parsedDate; console.log( "📅 [CHECKOUT] Data convertida com sucesso:", shippingDate ); } else { console.warn( "📅 [CHECKOUT] Data inválida após conversão:", dataDelivery.dateDelivery ); } } else if (dataDelivery.dateDelivery instanceof Date) { // Se já for Date (improvável após JSON.parse, mas por segurança) shippingDate = dataDelivery.dateDelivery; } } else { console.log("📅 [CHECKOUT] dateDelivery é null ou undefined"); } setNotesForm((prev) => ({ ...prev, shippingDate: shippingDate, scheduleDelivery: dataDelivery.scheduleDelivery || false, shippingPriority: dataDelivery.priorityDelivery || "B", notesText1: dataDelivery.notification || "", notesText2: dataDelivery.notification1 || "", notesDeliveryText1: dataDelivery.notificationDelivery1 || "", notesDeliveryText2: dataDelivery.notificationDelivery2 || "", notesDeliveryText3: dataDelivery.notificationDelivery3 || "", })); console.log( "📅 [CHECKOUT] Estado notesForm atualizado com shippingDate:", shippingDate ); } catch (e) { console.error("Erro ao carregar dados de entrega:", e); } } else { console.log( "📅 [CHECKOUT] Nenhum dado de entrega encontrado no localStorage" ); } // Carregar taxa e desconto const savedTaxDelivery = localStorage.getItem("taxDelivery"); if (savedTaxDelivery) { try { const taxDelivery = JSON.parse(savedTaxDelivery); setTaxValue(taxDelivery.taxValue?.toFixed(2) || "0"); } catch (e) { console.error("Erro ao carregar taxa de entrega:", e); } } }; loadSavedData(); }, []); // Carregar dados de pagamento quando cliente for selecionado useEffect(() => { const loadPaymentData = async () => { if (!selectedCustomer) return; setIsLoadingPaymentData(true); try { const user = authService.getUser(); const storesData = await lookupService.getStores(user?.id?.toString()); setStores(storesData); if (!paymentForm.invoiceStore && user?.store) { const defaultStore = storesData.find((s) => s.id === user.store); if (defaultStore) { setPaymentForm((prev) => ({ ...prev, invoiceStore: defaultStore })); localStorage.setItem("invoiceStore", JSON.stringify(defaultStore)); } } const billingsData = await lookupService.getBillings( selectedCustomer.customerId ); setBillings(billingsData); const partnersData = await lookupService.getPartners(); setPartners(partnersData); } catch (error) { console.error("Erro ao carregar dados de pagamento:", error); } finally { setIsLoadingPaymentData(false); } }; loadPaymentData(); }, [selectedCustomer]); // Recalcular taxa de entrega quando a prioridade de entrega mudar useEffect(() => { // Ignorar primeira renderização if (isFirstPriorityRender) { console.log("🔄 [TAX] Primeira renderização - ignorando cálculo"); setIsFirstPriorityRender(false); return; } const recalculateTaxOnPriorityChange = async () => { console.log("🔄 [TAX] ========================================"); console.log("🔄 [TAX] Iniciando recálculo de taxa de entrega"); console.log( "🔄 [TAX] Prioridade selecionada:", notesForm.shippingPriority ); console.log("🔄 [TAX] Itens no carrinho:", cart.length); // Verificar se há itens para entrega // Se deliveryType não estiver definido, considerar todos os itens como entrega const itemsDelivery = cart.filter((item) => { // Se não tem deliveryType definido, considerar como entrega if (!item.deliveryType) { return true; } // Se tem deliveryType, verificar se é EN ou EF return item.deliveryType === "EN" || item.deliveryType === "EF"; }); console.log( "🔄 [TAX] Itens de entrega encontrados:", itemsDelivery.length ); console.log( "🔄 [TAX] Detalhes dos itens:", cart.map((item) => ({ id: item.id, deliveryType: item.deliveryType || "NÃO DEFINIDO", description: item.description || item.name, code: item.code, })) ); // Se não há itens de entrega ou prioridade é 'M' (Retira), taxa é 0 if (itemsDelivery.length === 0 || notesForm.shippingPriority === "M") { console.log( "🔄 [TAX] Sem itens de entrega ou prioridade 'M' - Taxa = 0" ); setTaxValue("0"); setDeliveryTaxId(null); setCarrierId(null); localStorage.setItem( "taxDelivery", JSON.stringify({ taxValue: 0, deliveryTaxId: null, carrierId: null }) ); setShowCalculatingTaxModal(false); return; } // Se há itens de entrega e prioridade não é 'M', recalcular // Mas só se já temos cliente e endereço selecionados const customer = JSON.parse( localStorage.getItem("customer") || "null" ) as Customer | null; const addressCustomer = JSON.parse( localStorage.getItem("address") || "null" ); console.log( "🔄 [TAX] Cliente:", customer ? `ID ${customer.customerId}` : "não selecionado" ); console.log( "🔄 [TAX] Endereço:", addressCustomer ? "selecionado" : "não selecionado" ); if (!customer || (!addressCustomer && !customer.cityId)) { console.log( "🔄 [TAX] Dados insuficientes - aguardando cliente/endereço" ); console.log( "🔄 [TAX] Cliente:", customer ? `ID ${customer.customerId}` : "não encontrado" ); console.log( "🔄 [TAX] Endereço:", addressCustomer ? "encontrado" : "não encontrado" ); console.log("🔄 [TAX] customer.cityId:", customer?.cityId); setShowCalculatingTaxModal(false); setTaxErrorDialog({ title: "Taxa de Entrega", message: "É necessário selecionar um cliente e um endereço de entrega para calcular a taxa de entrega.", }); setShowTaxErrorDialog(true); return; } // Mostrar modal de loading console.log("🔄 [TAX] Mostrando modal de loading e iniciando cálculo..."); console.log( "🔄 [TAX] Estado antes de mostrar modal:", showCalculatingTaxModal ); setShowCalculatingTaxModal(true); console.log("🔄 [TAX] Estado após setShowCalculatingTaxModal(true)"); // Forçar re-render para mostrar o modal await new Promise((resolve) => setTimeout(resolve, 100)); console.log("🔄 [TAX] Após delay de 100ms"); // Recalcular automaticamente try { const invoiceStore = JSON.parse( localStorage.getItem("invoiceStore") || "null" ); let cartId = shoppingService.getCart() || ""; console.log("🔄 [TAX] CartId obtido do localStorage:", cartId); console.log("🔄 [TAX] Itens no carrinho local:", cart.length); console.log( "🔄 [TAX] Detalhes dos itens (IDs):", cart.map((item) => ({ id: item.id, idLength: item.id?.length })) ); // Se não houver cartId mas houver itens no carrinho, tentar criar o carrinho if (!cartId && cart.length > 0) { console.log( "🔄 [TAX] CartId não encontrado, mas há itens no carrinho. Tentando criar carrinho..." ); // Verificar se algum item já foi adicionado ao backend (tem ID longo, geralmente UUID) const itemWithBackendId = cart.find( (item) => item.id && item.id.length > 10 ); if (!itemWithBackendId) { // Se nenhum item foi adicionado ao backend, precisamos criar o primeiro item // para obter o cartId console.log( "🔄 [TAX] Nenhum item foi adicionado ao backend ainda. Criando primeiro item para obter cartId..." ); try { const firstItem = cart[0]; const shoppingItem = shoppingService.productToShoppingItem(firstItem); console.log("🔄 [TAX] Criando item no backend:", shoppingItem); const result = await shoppingService.createItemShopping( shoppingItem ); if (result.idCart) { cartId = result.idCart; console.log("🔄 [TAX] CartId criado com sucesso:", cartId); } else { console.error( "🔄 [TAX] CartId não foi retornado ao criar item. Resultado:", result ); setShowCalculatingTaxModal(false); setTaxErrorDialog({ title: "Taxa de Entrega", message: "Não foi possível criar o carrinho. Por favor, adicione os itens novamente.", type: "error", }); setShowTaxErrorDialog(true); return; } } catch (error: any) { console.error( "🔄 [TAX] Erro ao criar item para obter cartId:", error ); setShowCalculatingTaxModal(false); setTaxErrorDialog({ title: "Taxa de Entrega", message: "Não foi possível criar o carrinho. Por favor, adicione os itens novamente.", type: "error", }); setShowTaxErrorDialog(true); return; } } else { console.log( "🔄 [TAX] Item encontrado com ID do backend, mas cartId não está no localStorage." ); console.log( "🔄 [TAX] Isso pode indicar que o cartId foi perdido. Tentando recriar..." ); // Se há item com ID do backend, o cartId deveria estar no localStorage // Mas foi perdido. Vamos tentar criar um novo item para obter o cartId novamente try { const firstItem = cart[0]; const shoppingItem = shoppingService.productToShoppingItem(firstItem); // Não enviar idCart para criar um novo carrinho shoppingItem.idCart = undefined; console.log( "🔄 [TAX] Criando novo item sem idCart para obter novo cartId..." ); const result = await shoppingService.createItemShopping( shoppingItem ); if (result.idCart) { cartId = result.idCart; console.log("🔄 [TAX] Novo CartId criado:", cartId); } else { console.error("🔄 [TAX] Novo cartId não foi retornado"); } } catch (error: any) { console.error("🔄 [TAX] Erro ao recriar carrinho:", error); } } } const user = authService.getUser(); let storeId = invoiceStore ? invoiceStore.id : user?.store; let cityId = 0; let ibgeCode = 0; if (addressCustomer && addressCustomer.cityCode) { cityId = addressCustomer.cityCode; // Converter ibgeCode para número, garantindo que seja válido const ibgeCodeValue = addressCustomer.ibgeCode; if ( ibgeCodeValue && ibgeCodeValue !== "" && String(ibgeCodeValue).trim() !== "" ) { // Tentar converter para número const parsed = typeof ibgeCodeValue === "string" ? parseInt(ibgeCodeValue.trim(), 10) : Number(ibgeCodeValue); // Se for um número válido, usar; caso contrário, usar cityCode como fallback if (!isNaN(parsed) && parsed > 0) { ibgeCode = parsed; } else { // Se ibgeCode não for válido, usar cityCode como fallback ibgeCode = cityId; } } else { // Se não houver ibgeCode, usar cityCode como fallback ibgeCode = cityId; } console.log( "🔄 [TAX] Usando cityId e ibgeCode do endereço:", cityId, ibgeCode, "tipo:", typeof ibgeCode, "valor original:", ibgeCodeValue ); } else if (customer.cityId) { cityId = customer.cityId; // Converter ibgeCode do cliente para número const ibgeCodeValue = customer.ibgeCode; if ( ibgeCodeValue && (typeof ibgeCodeValue === "number" ? ibgeCodeValue > 0 : String(ibgeCodeValue).trim() !== "") ) { const parsed = typeof ibgeCodeValue === "number" ? ibgeCodeValue : parseInt(String(ibgeCodeValue).trim(), 10); if (!isNaN(parsed) && parsed > 0) { ibgeCode = parsed; } else { ibgeCode = cityId; } } else { ibgeCode = cityId; } console.log( "🔄 [TAX] Usando cityId e ibgeCode do cliente:", cityId, ibgeCode, "tipo:", typeof ibgeCode, "valor original:", ibgeCodeValue ); } if (cityId === 0 || ibgeCode === 0 || !cartId) { console.error( "🔄 [TAX] Dados incompletos - cityId:", cityId, "ibgeCode:", ibgeCode, "cartId:", cartId ); setShowCalculatingTaxModal(false); setTaxErrorDialog({ title: "Taxa de Entrega", message: `Dados incompletos: cityId=${cityId}, ibgeCode=${ibgeCode}, cartId=${ cartId ? "OK" : "FALTANDO" }. Verifique se o endereço está completo.`, }); setShowTaxErrorDialog(true); return; // Dados incompletos } const dataDeliveryTax: CalculateDeliveryTaxRequest = { cartId, cityId, ibgeCode, priorityDelivery: notesForm.shippingPriority, }; console.log("🔄 [TAX] Chamando API com dados:", dataDeliveryTax); const options = await orderService.calculateDeliveryTax( dataDeliveryTax ); console.log( "🔄 [TAX] Opções recebidas da API:", options.length, "opções" ); if (options.length > 0) { // Se já existe uma taxa selecionada, tentar manter if (deliveryTaxId) { console.log( "🔄 [TAX] Tentando manter taxa selecionada ID:", deliveryTaxId ); const selected = options.find((opt) => opt.id === deliveryTaxId); if (selected) { console.log("🔄 [TAX] Taxa mantida:", selected.deliveryValue); setTaxValue(selected.deliveryValue.toFixed(2)); setDeliveryTaxId(selected.id); setCarrierId(selected.carrierId); localStorage.setItem( "taxDelivery", JSON.stringify({ taxValue: selected.deliveryValue, deliveryTaxId: selected.id, carrierId: selected.carrierId, }) ); setShowCalculatingTaxModal(false); return; } } // Caso contrário, usar a primeira opção const firstOption = options[0]; console.log( "🔄 [TAX] Usando primeira opção:", firstOption.deliveryValue ); setTaxValue(firstOption.deliveryValue.toFixed(2)); setDeliveryTaxId(firstOption.id); setCarrierId(firstOption.carrierId); localStorage.setItem( "taxDelivery", JSON.stringify({ taxValue: firstOption.deliveryValue, deliveryTaxId: firstOption.id, carrierId: firstOption.carrierId, }) ); console.log( "🔄 [TAX] Taxa atualizada com sucesso:", firstOption.deliveryValue.toFixed(2) ); } else { console.warn("🔄 [TAX] Nenhuma opção de taxa retornada pela API"); setShowCalculatingTaxModal(false); setTaxErrorDialog({ title: "Taxa de Entrega", message: "Não foram encontradas opções de taxa de entrega para os dados informados. Verifique o endereço de entrega.", type: "info", }); setShowTaxErrorDialog(true); } setShowCalculatingTaxModal(false); console.log("🔄 [TAX] ========================================"); } catch (error: any) { console.error("🔄 [TAX] Erro ao recalcular taxa de entrega:", error); console.error("🔄 [TAX] Stack trace:", error?.stack); setShowCalculatingTaxModal(false); setTaxErrorDialog({ title: "Taxa de Entrega", message: error?.message || "Ocorreu um erro ao calcular a taxa de entrega. Por favor, tente novamente.", }); setShowTaxErrorDialog(true); } }; recalculateTaxOnPriorityChange(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [notesForm.shippingPriority]); // Carregar planos de pagamento quando cobrança for selecionada useEffect(() => { const loadPaymentPlans = async () => { if (!paymentForm.billing) { setPaymentPlans([]); return; } try { const plans = await lookupService.getPaymentPlans( paymentForm.billing.codcob ); setPaymentPlans(plans); } catch (error) { console.error("Erro ao carregar planos de pagamento:", error); } }; loadPaymentPlans(); }, [paymentForm.billing]); // Carregar endereços do cliente quando cliente for selecionado useEffect(() => { const loadCustomerAddresses = async () => { if (!selectedCustomer) { setCustomerAddresses([]); return; } try { const addresses = await customerService.getCustomerAddresses( selectedCustomer.customerId ); setCustomerAddresses(addresses); } catch (error) { console.error("Erro ao carregar endereços do cliente:", error); } }; loadCustomerAddresses(); }, [selectedCustomer]); // Buscar clientes const handleSearchCustomers = useCallback(async () => { if (customerSearchTerm.length < 3) { setCustomerSearchResults([]); return; } setIsSearchingCustomers(true); try { const results = await customerService.searchCustomers(customerSearchTerm); setCustomerSearchResults(results); } catch (error) { console.error("Erro ao buscar clientes:", error); setCustomerSearchResults([]); } finally { setIsSearchingCustomers(false); } }, [customerSearchTerm]); useEffect(() => { const timer = setTimeout(() => { handleSearchCustomers(); }, 500); return () => clearTimeout(timer); }, [customerSearchTerm, handleSearchCustomers]); // Selecionar cliente const handleSelectCustomer = (customer: Customer) => { console.log("Cliente selecionado:", customer); // Se um cliente diferente foi selecionado, limpar o endereço if ( selectedCustomer && selectedCustomer.customerId !== customer.customerId ) { console.log( "🔄 [CHECKOUT] Cliente diferente selecionado, limpando endereço..." ); setSelectedAddress(null); setAddressForm({ zipCode: "", address: "", number: "", city: "", state: "", complement: "", referencePoint: "", note: "", }); localStorage.removeItem("address"); setCustomerAddresses([]); } setSelectedCustomer(customer); const formData = { name: customer.name || "", document: customer.cpfCnpj || customer.document || "", cellPhone: customer.cellPhone || customer.phone || "", cep: customer.zipCode || customer.cep || "", address: customer.address || "", number: customer.addressNumber || customer.number || "", city: customer.city || "", state: customer.state || "", complement: customer.complement || "", }; setCustomerForm(formData); localStorage.setItem("customer", JSON.stringify(customer)); setShowCustomerModal(false); setCustomerSearchTerm(""); setCustomerSearchResults([]); setCustomerErrors({}); }; // Navegação entre passos const goToStep = (step: Step) => { setCurrentStep(step); }; const handleNextStep = () => { if (currentStep === "customer") { const validation = validateCustomerForm(customerForm); if (!validation.isValid) { setCustomerErrors(validation.errors); return; } if (!selectedCustomer) { setInfoModal({ title: "Pedido de venda", message: "Atenção! Não foi informado um cliente para a venda.", description: "Para gerar um pedido de venda é necessário selecionar um cliente.", }); setShowInfoModal(true); return; } if (selectedCustomer.place?.id === 19) { setInfoModal({ title: "Pedido de venda", message: "ATENÇÃO! Cliente com praça do E-COMMERCE!", description: "Altere a praça do cliente pela opção NOVO CLIENTE para continuar.", }); setShowInfoModal(true); return; } if ( selectedCustomer.place?.name === "INATIVO" || selectedCustomer.place?.name === "INATIVA" ) { setInfoModal({ title: "Pedido de venda", message: "ATENÇÃO! Cliente com praça INATIVA!", description: "Altere a praça do cliente pela opção NOVO CLIENTE para continuar.", }); setShowInfoModal(true); return; } setCustomerErrors({}); goToStep("payment"); } else if (currentStep === "payment") { const validation = validatePaymentForm(paymentForm); if (!validation.isValid) { setPaymentErrors(validation.errors); return; } setPaymentErrors({}); goToStep("address"); } else if (currentStep === "address") { const validation = validateAddressForm(addressForm); if (!validation.isValid) { setAddressErrors(validation.errors); return; } if (selectedAddress) { localStorage.setItem("address", JSON.stringify(selectedAddress)); } else { const tempAddress: CustomerAddress = { zipCode: addressForm.zipCode, address: addressForm.address, number: addressForm.number, city: addressForm.city, state: addressForm.state, complement: addressForm.complement, referencePoint: addressForm.referencePoint, note: addressForm.note, }; localStorage.setItem("address", JSON.stringify(tempAddress)); } setAddressErrors({}); goToStep("notes"); } }; const handlePreviousStep = () => { if (currentStep === "payment") { goToStep("customer"); } else if (currentStep === "address") { goToStep("payment"); } else if (currentStep === "notes") { goToStep("address"); } }; // Handlers de pagamento const handleSetBilling = (billing: Billing | null) => { setPaymentForm((prev) => ({ ...prev, billing, paymentPlan: null, })); if (billing) { localStorage.setItem("billing", JSON.stringify(billing)); localStorage.removeItem("paymentPlan"); } else { localStorage.removeItem("billing"); } }; const handleSetPaymentPlan = (paymentPlan: PaymentPlan | null) => { setPaymentForm((prev) => ({ ...prev, paymentPlan })); if (paymentPlan) { localStorage.setItem("paymentPlan", JSON.stringify(paymentPlan)); } else { localStorage.removeItem("paymentPlan"); } }; const handleSetInvoiceStore = (store: StoreERP | null) => { setPaymentForm((prev) => ({ ...prev, invoiceStore: store })); if (store) { localStorage.setItem("invoiceStore", JSON.stringify(store)); } else { localStorage.removeItem("invoiceStore"); } }; const handleSetPartner = (partner: PartnerSales | null) => { setPaymentForm((prev) => ({ ...prev, partner })); if (partner) { localStorage.setItem("partner", JSON.stringify(partner)); } else { localStorage.removeItem("partner"); } }; // Salvar dados de entrega const saveDeliveryData = () => { const dataDelivery = { dateDelivery: notesForm.shippingDate ? notesForm.shippingDate.toISOString() : null, scheduleDelivery: notesForm.scheduleDelivery, priorityDelivery: notesForm.shippingPriority, notification: notesForm.notesText1, notification1: notesForm.notesText2, notificationDelivery1: notesForm.notesDeliveryText1, notificationDelivery2: notesForm.notesDeliveryText2, notificationDelivery3: notesForm.notesDeliveryText3, }; localStorage.setItem("dataDelivery", JSON.stringify(dataDelivery)); }; // Criar pedido // Converter OrderItem[] para CartItensModel[] (igual ao Angular getItens()) const getItens = useCallback((): CartItensModel[] => { return cart.map((item) => { // Obter idProduct (pode vir de item.code, item.id ou item.idProduct) let idProduct = 0; if (item.idProduct) { idProduct = typeof item.idProduct === "number" ? item.idProduct : parseInt(String(item.idProduct), 10); } else if (item.code) { idProduct = parseInt(String(item.code), 10); } else if (item.id && !item.id.includes("-")) { // Se id não é UUID, tentar converter idProduct = parseInt(String(item.id), 10); } // Obter EAN (pode vir de item.ean ou usar idProduct como fallback) let ean = idProduct; if (item.ean) { const parsedEan = parseInt(String(item.ean), 10); ean = isNaN(parsedEan) ? idProduct : parsedEan; } // Obter stockStore (pode vir de item.stockStore ou item.stockLocal) const stockStore = item.stockStore ? String(item.stockStore) : item.stockLocal ? String(item.stockLocal) : ""; return { idProduct, ean, idStock: stockStore, deliveryMethod: item.deliveryType || "EN", quantity: item.quantity || 1, listPrice: item.originalPrice || item.listPrice || item.price || 0, salePrice: item.price || 0, descriptionAux: item.auxDescription || null, environment: item.environment || null, }; }); }, [cart]); // Validação completa do pedido (igual ao Angular orderValid()) const orderValid = useCallback((): boolean => { const customer = JSON.parse( localStorage.getItem("customer") || "null" ) as Customer | null; if (!customer) { setConfirmDialog({ type: "warning", title: "Gravar pedido de venda", message: "Atenção! Não foi informado um cliente para a venda.\n\nPara gerar um pedido de venda é necessário selecionar um cliente.", onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); return false; } const paymentPlan = JSON.parse( localStorage.getItem("paymentPlan") || "null" ) as PaymentPlan | null; if (!paymentPlan) { setConfirmDialog({ type: "warning", title: "Gravar pedido de venda", message: "Atenção! Não foi informado um plano de pagamento para a venda.\n\nPara gerar um pedido de venda é necessário selecionar um plano de pagamento para o pedido.", onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); return false; } const billing = JSON.parse( localStorage.getItem("billing") || "null" ) as Billing | null; if (!billing) { setConfirmDialog({ type: "warning", title: "Gravar pedido de venda", message: "Atenção! Não foi informado uma cobrança para a venda.\n\nPara gerar um pedido de venda é necessário selecionar uma cobrança.", onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); return false; } return true; }, []); const handleCreateOrder = async () => { // Validação básica const validation = validateOrder(); if (!validation.isValid) { const fullMessage = validation.description ? `${validation.message}\n\n${validation.description}` : validation.message; setConfirmDialog({ type: "warning", title: "Gravar pedido de venda", message: fullMessage, onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); return; } // Validação completa (igual ao Angular) if (!orderValid()) { return; } // Verificar se há data de entrega if (!notesForm.shippingDate) { setConfirmDialog({ type: "warning", title: "Pedido de venda", message: "Não foi possível gerar o pedido de venda!\n\nFavor informe a data de entrega do pedido.", onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); return; } // Salvar dados de entrega saveDeliveryData(); // Preparar mensagem de confirmação com informações de entrega let informationDelivery = ""; if (notesForm.shippingDate) { const dateDelivery = new Date(notesForm.shippingDate); const dataTexto = dateDelivery.getDate().toString().padStart(2, "0") + "/" + (dateDelivery.getMonth() + 1).toString().padStart(2, "0") + "/" + dateDelivery.getFullYear().toString(); informationDelivery = `Pedido com data de entrega para o dia ${dataTexto} - Tipo de entrega: ${ notesForm.scheduleDelivery ? "PROGRAMADA" : "NORMAL" }`; } // Mostrar confirmação setConfirmDialog({ type: "confirm", title: "Gerar pedido de venda", message: "Confirma a geração do pedido de venda?", onConfirm: async () => { setShowConfirmDialog(false); setIsLoadingOrder(true); try { const customer = JSON.parse( localStorage.getItem("customer")! ) as Customer; const address = JSON.parse( localStorage.getItem("address") || "null" ) as CustomerAddress | null; const paymentPlan = JSON.parse( localStorage.getItem("paymentPlan")! ) as PaymentPlan; const billing = JSON.parse( localStorage.getItem("billing")! ) as Billing; const invoiceStore = JSON.parse( localStorage.getItem("invoiceStore")! ) as StoreERP; const partner = JSON.parse( localStorage.getItem("partner") || "null" ) as PartnerSales | null; const preCustomer = JSON.parse( localStorage.getItem("preCustomer") || "null" ); const user = authService.getUser(); // Validações adicionais (igual ao Angular) if (!invoiceStore) { setConfirmDialog({ type: "warning", title: "Pedido de venda", message: "Não foi possível gerar o pedido de venda!\n\nFavor selecione uma filial de venda para o orçamento.", onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); setIsLoadingOrder(false); return; } // Converter itens do carrinho const itensCart = getItens(); // Preparar dados do pré-cadastro (se consumidor final) let preCadastroDocument = ""; let preCadastroName = ""; let preCadastroPhone = ""; if (preCustomer) { preCadastroDocument = preCustomer.document || ""; preCadastroName = preCustomer.name || ""; preCadastroPhone = preCustomer.phone || ""; } // Obter idAddress const idAddress = address ? address.idAddress || address.addressId || 0 : 0; // Obter carrierId const savedTaxDelivery = localStorage.getItem("taxDelivery"); let carrierIdValue = 0; if (savedTaxDelivery) { try { const taxDelivery = JSON.parse(savedTaxDelivery); carrierIdValue = taxDelivery.carrierId || 0; } catch (e) { console.error("Erro ao parsear taxDelivery:", e); } } // Montar CartModel (igual ao Angular) const cartModel: CartModel = { id: shoppingService.getCart() || undefined, userId: user?.id ? typeof user.id === "number" ? user.id : parseInt(String(user.id), 10) : undefined, saleStore: invoiceStore.id, idCustomer: customer.customerId, idPaymentPlan: paymentPlan.codplpag, idBilling: billing.codcob, idSeller: authService.getSeller() ? typeof authService.getSeller() === "number" ? authService.getSeller() : parseInt(String(authService.getSeller()), 10) : undefined, idProfessional: partner ? partner.id : 0, scheduleDelivery: notesForm.scheduleDelivery, shippingDate: notesForm.shippingDate, shippingPriority: notesForm.shippingPriority, idStorePlace: notesForm.shippingPriority === "M" ? null : null, // TODO: Implementar storePlaceSelected shippingValue: taxValueNum, idAddress, notation1: notesForm.notesText1 || "", notation2: notesForm.notesText2 || "", notation3: "", deliveryNote1: notesForm.notesDeliveryText1 || "", deliveryNote2: notesForm.notesDeliveryText2 || "", deliveryNote3: notesForm.notesDeliveryText3 || "", itens: itensCart, preCustomerDocument: customer.customerId === 1 ? preCadastroDocument : null, preCustomerName: customer.customerId === 1 ? preCadastroName : null, preCustomerPhone: customer.customerId === 1 ? preCadastroPhone : null, carrierId: carrierIdValue, }; console.log( "📦 [ORDER] Criando pedido com dados:", JSON.stringify(cartModel, null, 2) ); const result = await orderService.createOrder(cartModel); if (result.success && result.orderId) { setConfirmDialog({ type: "success", title: "Gravar pedido de venda", message: `Pedido de venda gerado com sucesso!\n\nNúmero do pedido gerado: ${result.orderId}`, onConfirm: () => { setShowConfirmDialog(false); // Limpar carrinho usando o hook (se disponível) if (onClearCart) { onClearCart(); } // Limpar dados do localStorage shoppingService.clearShoppingData(); onBack(); }, }); setShowConfirmDialog(true); } else { setConfirmDialog({ type: "error", title: "Gravar pedido de venda", message: result.message || "Ops, houve um problema na geração do pedido de venda.\n\nTente novamente ou entre em contato com o suporte.", onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); } } catch (error: any) { console.error("📦 [ORDER] Erro ao criar pedido:", error); // Extrair mensagem de erro mais detalhada let errorMessage = error.message || "Ops, houve um problema na geração do pedido de venda."; let errorDescription = "Tente novamente ou entre em contato com o suporte."; // Se houver errorData com mais detalhes if (error.errorData) { const errorData = error.errorData; // Se houver errors object, formatar if (errorData.errors && typeof errorData.errors === "object") { const errorDetails = Object.entries(errorData.errors) .map(([key, value]) => { if (Array.isArray(value)) { return `${key}: ${value.join(", ")}`; } return `${key}: ${value}`; }) .join("\n"); if (errorDetails) { errorDescription = errorDetails; } } // Se houver data com mensagem if (errorData.data && typeof errorData.data === "string") { errorMessage = errorData.data; } else if (errorData.message) { errorMessage = errorData.message; } } // Combinar mensagem e descrição em uma única mensagem const fullMessage = errorDescription && errorDescription !== "Tente novamente ou entre em contato com o suporte." ? `${errorMessage}\n\n${errorDescription}` : errorMessage; setConfirmDialog({ type: "error", title: "Gravar pedido de venda", message: fullMessage, onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); } finally { setIsLoadingOrder(false); } }, }); setShowConfirmDialog(true); }; // Criar orçamento const handleCreatePreOrder = async () => { // Validação básica const validation = validateOrder(); if (!validation.isValid) { const fullMessage = validation.description ? `${validation.message}\n\n${validation.description}` : validation.message; setConfirmDialog({ type: "warning", title: "Gravar orçamento", message: fullMessage, onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); return; } // Validação completa (igual ao Angular) if (!orderValid()) { return; } // Verificar se há data de entrega if (!notesForm.shippingDate) { setConfirmDialog({ type: "warning", title: "Pedido de venda", message: "Não foi possível gerar o pedido de venda!\n\nFavor informe a data de entrega do pedido.", onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); return; } // Salvar dados de entrega saveDeliveryData(); // Mostrar confirmação setConfirmDialog({ type: "confirm", title: "Gravar orçamento", message: "Deseja gravar como orçamento?", onConfirm: async () => { setShowConfirmDialog(false); setIsLoadingPreOrder(true); try { const customer = JSON.parse( localStorage.getItem("customer")! ) as Customer; const address = JSON.parse( localStorage.getItem("address") || "null" ) as CustomerAddress | null; const paymentPlan = JSON.parse( localStorage.getItem("paymentPlan")! ) as PaymentPlan; const billing = JSON.parse( localStorage.getItem("billing")! ) as Billing; const invoiceStore = JSON.parse( localStorage.getItem("invoiceStore")! ) as StoreERP; const partner = JSON.parse( localStorage.getItem("partner") || "null" ) as PartnerSales | null; const preCustomer = JSON.parse( localStorage.getItem("preCustomer") || "null" ); const user = authService.getUser(); // Validações adicionais (igual ao Angular) if (!invoiceStore) { setConfirmDialog({ type: "warning", title: "Pedido de venda", message: "Não foi possível gerar o pedido de venda!\n\nFavor selecione uma filial de venda para o orçamento.", onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); setIsLoadingPreOrder(false); return; } // Converter itens do carrinho const itensCart = getItens(); // Obter idAddress const idAddress = address ? address.idAddress || address.addressId || 0 : 0; // Obter carrierId const savedTaxDelivery = localStorage.getItem("taxDelivery"); let carrierIdValue = 0; if (savedTaxDelivery) { try { const taxDelivery = JSON.parse(savedTaxDelivery); carrierIdValue = taxDelivery.carrierId || 0; } catch (e) { console.error("Erro ao parsear taxDelivery:", e); } } // Montar CartModel (igual ao Angular) const cartModel: CartModel = { id: shoppingService.getCart() || undefined, userId: user?.id ? typeof user.id === "number" ? user.id : parseInt(String(user.id), 10) : undefined, saleStore: invoiceStore.id, idCustomer: customer.customerId, idPaymentPlan: paymentPlan.codplpag, idBilling: billing.codcob, idSeller: authService.getSeller() ? typeof authService.getSeller() === "number" ? authService.getSeller() : parseInt(String(authService.getSeller()), 10) : undefined, idProfessional: partner ? partner.id : 0, shippingDate: notesForm.shippingDate, scheduleDelivery: notesForm.scheduleDelivery, shippingPriority: notesForm.shippingPriority, shippingValue: taxValueNum, carrierId: carrierIdValue || 0, idAddress, notation1: notesForm.notesText1 || "", notation2: notesForm.notesText2 || "", notation3: "", deliveryNote1: notesForm.notesDeliveryText1 || "", deliveryNote2: notesForm.notesDeliveryText2 || "", deliveryNote3: notesForm.notesDeliveryText3 || "", itens: itensCart, preCustomerDocument: customer.customerId === 1 ? preCustomer?.document || null : null, preCustomerName: customer.customerId === 1 ? preCustomer?.name || null : null, preCustomerPhone: customer.customerId === 1 ? preCustomer?.phone || null : null, }; console.log( "📋 [PREORDER] Criando orçamento com dados:", JSON.stringify(cartModel, null, 2) ); const result = await orderService.createPreOrder(cartModel); if (result.success && result.preOrderId) { setConfirmDialog({ type: "success", title: "Gravar pedido de venda", message: `Orçamento gerado com sucesso!\n\nNúmero do orçamento gerado: ${result.preOrderId}`, onConfirm: () => { setShowConfirmDialog(false); // Limpar carrinho usando o hook (se disponível) if (onClearCart) { onClearCart(); } // Limpar dados do localStorage shoppingService.clearShoppingData(); onBack(); }, }); setShowConfirmDialog(true); } else { setConfirmDialog({ type: "error", title: "Gravar pedido de venda", message: result.message || "Houve um erro ao gerar o orçamento!\n\nTente novamente ou entre em contato com o suporte.", onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); } } catch (error: any) { console.error("📋 [PREORDER] Erro ao criar orçamento:", error); // Extrair mensagem de erro mais detalhada let errorMessage = error.message || "Houve um erro ao gerar o orçamento!"; let errorDescription = "Tente novamente ou entre em contato com o suporte."; // Se houver errorData com mais detalhes if (error.errorData) { const errorData = error.errorData; // Se houver errors object, formatar if (errorData.errors && typeof errorData.errors === "object") { const errorDetails = Object.entries(errorData.errors) .map(([key, value]) => { if (Array.isArray(value)) { return `${key}: ${value.join(", ")}`; } return `${key}: ${value}`; }) .join("\n"); if (errorDetails) { errorDescription = errorDetails; } } // Se houver data com mensagem if (errorData.data && typeof errorData.data === "string") { errorMessage = errorData.data; } else if (errorData.message) { errorMessage = errorData.message; } } // Combinar mensagem e descrição em uma única mensagem const fullMessage = errorDescription && errorDescription !== "Tente novamente ou entre em contato com o suporte." ? `${errorMessage}\n\n${errorDescription}` : errorMessage; setConfirmDialog({ type: "error", title: "Gravar pedido de venda", message: fullMessage, onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); } finally { setIsLoadingPreOrder(false); } }, }); setShowConfirmDialog(true); }; // Handlers de formulário const handleCustomerFormChange = (field: string, value: string) => { setCustomerForm((prev) => ({ ...prev, [field]: value })); if (customerErrors[field]) { setCustomerErrors((prev) => { const newErrors = { ...prev }; delete newErrors[field]; return newErrors; }); } }; const handleAddressFormChange = (field: string, value: string) => { setAddressForm((prev) => ({ ...prev, [field]: value })); if (addressErrors[field]) { setAddressErrors((prev) => { const newErrors = { ...prev }; delete newErrors[field]; return newErrors; }); } }; const handleNotesFormChange = (field: string, value: any) => { // Validar data mínima (D+3) quando o campo shippingDate for alterado if (field === "shippingDate" && value) { const today = new Date(); today.setHours(0, 0, 0, 0); // Zerar horas para comparação apenas de data // Calcular data mínima (D+3) const minDate = new Date(today); minDate.setDate(today.getDate() + 3); minDate.setHours(0, 0, 0, 0); // Normalizar a data selecionada para comparação (apenas data, sem hora) // Criar uma nova data local a partir dos componentes da data recebida const selectedDate = new Date( value.getFullYear(), value.getMonth(), value.getDate() ); selectedDate.setHours(0, 0, 0, 0); console.log("📅 [CHECKOUT] Validando data de entrega:"); console.log("📅 [CHECKOUT] Data selecionada:", selectedDate); console.log("📅 [CHECKOUT] Data mínima (D+3):", minDate); console.log( "📅 [CHECKOUT] Data selecionada < Data mínima?", selectedDate < minDate ); // Se a data selecionada for menor que D+3, mostrar dialog e ajustar if (selectedDate < minDate) { console.log("📅 [CHECKOUT] Data inválida! Ajustando para D+3"); setShowDateValidationDialog(true); // Ajustar para D+3 setNotesForm((prev) => ({ ...prev, [field]: minDate })); saveDeliveryData(); return; } } setNotesForm((prev) => ({ ...prev, [field]: value })); saveDeliveryData(); }; const handleRemoveAddress = () => { setSelectedAddress(null); setAddressForm({ zipCode: "", address: "", number: "", city: "", state: "", complement: "", referencePoint: "", note: "", }); localStorage.removeItem("address"); }; // Calcular taxa de entrega const handleChangeTax = async () => { // Verificar se há itens para entrega const itemsDelivery = cart.filter( (item) => item.deliveryType === "EN" || item.deliveryType === "EF" ); if (itemsDelivery.length === 0 || notesForm.shippingPriority === "M") { setTaxValue("0"); localStorage.setItem("taxDelivery", JSON.stringify({ taxValue: 0 })); return; } // Buscar dados necessários const customer = JSON.parse( localStorage.getItem("customer") || "{}" ) as Customer; const addressCustomer = JSON.parse( localStorage.getItem("address") || "null" ); const invoiceStore = JSON.parse( localStorage.getItem("invoiceStore") || "null" ); const cartId = shoppingService.getCart() || ""; if (!cartId) { setTaxErrorDialog({ title: "Taxa de Entrega", message: "Não foi possível identificar o carrinho. Tente novamente.", }); setShowTaxErrorDialog(true); return; } const user = authService.getUser(); let storeId = invoiceStore ? invoiceStore.id : user?.store; let cityId = 0; let ibgeCode = 0; if (addressCustomer && addressCustomer.cityCode) { cityId = addressCustomer.cityCode; ibgeCode = addressCustomer.ibgeCode; } else if (customer.cityId) { cityId = customer.cityId; ibgeCode = parseInt(String(customer.ibgeCode || "0")); } if (cityId === 0 || ibgeCode === 0) { setInfoModal({ title: "Taxa de Entrega", message: "Dados de endereço incompletos", description: "É necessário informar um endereço completo para calcular a taxa de entrega.", }); setShowInfoModal(true); return; } const dataDeliveryTax: CalculateDeliveryTaxRequest = { cartId, cityId, ibgeCode, priorityDelivery: notesForm.shippingPriority, }; setIsLoadingDeliveryTax(true); setShowDeliveryTaxModal(true); try { const options = await orderService.calculateDeliveryTax(dataDeliveryTax); setDeliveryTaxOptions(options); // Se já existe uma taxa selecionada, tentar manter if (deliveryTaxId && options.length > 0) { const selected = options.find((opt) => opt.id === deliveryTaxId); if (selected) { setTaxValue(selected.deliveryValue.toFixed(2)); setCarrierId(selected.carrierId); localStorage.setItem( "taxDelivery", JSON.stringify({ taxValue: selected.deliveryValue, deliveryTaxId: selected.id, carrierId: selected.carrierId, }) ); } } } catch (error: any) { console.error("Erro ao calcular taxa de entrega:", error); setTaxErrorDialog({ title: "Taxa de Entrega", message: error.message || "Ocorreu um erro ao calcular a taxa de entrega. Por favor, tente novamente.", }); setShowTaxErrorDialog(true); setShowDeliveryTaxModal(false); } finally { setIsLoadingDeliveryTax(false); } }; const handleSelectDeliveryTax = (deliveryTax: DeliveryTaxTable) => { setTaxValue(deliveryTax.deliveryValue.toFixed(2)); setDeliveryTaxId(deliveryTax.id); setCarrierId(deliveryTax.carrierId); localStorage.setItem( "taxDelivery", JSON.stringify({ taxValue: deliveryTax.deliveryValue, deliveryTaxId: deliveryTax.id, carrierId: deliveryTax.carrierId, }) ); }; // Aplicar desconto const handleApplyDiscount = () => { const payment = JSON.parse( localStorage.getItem("paymentPlan") || "null" ) as PaymentPlan | null; if (!payment) { setInfoModal({ title: "Desconto para o pedido", message: "Venda sem plano de pagamento informado, desconto não permitido!", description: "Selecione primeiro um plano de pagamento para aplicar um desconto!", }); setShowInfoModal(true); return; } const isManager = authService.isManager(); if (payment.numdias && payment.numdias > 30 && !isManager) { setInfoModal({ title: "Desconto para o pedido", message: "Plano de pagamento parcelado, desconto não permitido!", description: "Desconto autorizado apenas para venda a vista ou em 1x no cartão!", }); setShowInfoModal(true); return; } // Calcular valor do pedido (sem promoções) const productValue = cart .filter((item) => !item.promotion || item.promotion === 0) .reduce((acc, item) => acc + item.price * item.quantity, 0); if (productValue <= 0) { setInfoModal({ title: "Pedido de venda", message: "Não existem itens disponíveis para aplicar desconto !", description: "Não existem itens adicionados no carrinho ou todos os itens estão em promoção!", }); setShowInfoModal(true); return; } // Calcular lucro const profit = cart .filter((item) => !item.promotion || item.promotion === 0) .reduce((acc, item) => acc + (item.cost || 0) * item.quantity, 0); const percentProfit = productValue > 0 ? (((productValue - profit) / productValue) * 100).toFixed(2) : "0.00"; // Obter desconto atual const currentDiscount = parseFloat(discountValue.replace(/[^\d,.-]/g, "").replace(",", ".")) || 0; setShowDiscountModal(true); }; // Handlers para ações dos itens do carrinho const handleEditItem = async (item: OrderItem) => { try { setSelectedItemForEdit(item); setShowProductEditModal(true); // O EditItemModal vai carregar os detalhes do produto internamente } catch (error: any) { console.error("Erro ao abrir modal de edição:", error); setConfirmDialog({ type: "error", title: "Erro", message: error.message || "Não foi possível abrir o modal de edição.", onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); } }; const handleDiscountItem = (item: OrderItem) => { // Validações antes de abrir o modal const paymentPlan = localStorage.getItem("paymentPlan"); if (!paymentPlan) { setConfirmDialog({ type: "warning", title: "Desconto sobre item de venda", message: "Venda sem plano de pagamento informado, desconto não permitido!", onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); return; } if (item.promotion && item.promotion > 0) { setConfirmDialog({ type: "warning", title: "Desconto sobre item de venda", message: "Produto em promoção, desconto não permitido!", onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); return; } setSelectedItemForDiscount(item); setShowDiscountItemModal(true); }; const handleRemoveItem = (item: OrderItem) => { setSelectedItemForRemove(item); setConfirmDialog({ type: "delete", title: "Pedido de venda", message: "Confirma a exclusão do item do pedido?", onConfirm: async () => { try { // O item.id deve ser o ID do item no carrinho (retornado pelo backend) // Verificar se é um UUID (geralmente tem mais de 10 caracteres) ou se é o ID do backend if (item.id) { await shoppingService.deleteItemShopping(item.id); } else { console.error("Item sem ID válido para remoção:", item); throw new Error("Item não possui ID válido para remoção"); } setShowConfirmDialog(false); setSelectedItemForRemove(null); // Atualizar carrinho após remoção bem-sucedida if (onCartUpdate) { await onCartUpdate(); } else { window.location.reload(); } } catch (error: any) { console.error("Erro ao remover item:", error); setShowConfirmDialog(false); setSelectedItemForRemove(null); // Usar ConfirmDialog com type "error" em vez de InfoModal setConfirmDialog({ type: "error", title: "Erro", message: error.message || "Não foi possível remover o item do carrinho.", onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); } }, }); setShowConfirmDialog(true); }; const handleDiscountItemConfirm = () => { // Atualizar carrinho após aplicar desconto if (onCartUpdate) { onCartUpdate(); } else { window.location.reload(); } }; const handleConfirmDiscount = ( discountValueNum: number, discountPercentNum: number ) => { setDiscountValue( new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL", }) .format(discountValueNum) .replace("R$", "") .trim() ); localStorage.setItem( "discount", JSON.stringify({ discountValue: discountValueNum }) ); }; return (
{/* Header */}

Resumo do Pedido

{/* Tabela de Produtos */}
{/* Esquerda: Formulários do Wizard */}
{currentStep === "customer" && ( setShowCreateCustomerModal(true)} onShowSearchModal={() => setShowCustomerModal(true)} onNext={handleNextStep} /> )} {currentStep === "payment" && ( )} {currentStep === "address" && ( setShowAddressModal(true)} onRemoveAddress={handleRemoveAddress} onSelectAddress={(address) => { setSelectedAddress(address); setAddressForm({ zipCode: address.zipCode || "", address: address.address || "", number: address.number || "", city: address.city || "", state: address.state || "", complement: address.complement || "", referencePoint: address.referencePoint || "", note: address.note || "", }); localStorage.setItem("address", JSON.stringify(address)); }} onPrevious={handlePreviousStep} onNext={handleNextStep} /> )} {currentStep === "notes" && ( )}
{/* Direita: Revisar Detalhes do Pedido */}
{/* Modais */} setShowCreateCustomerModal(false)} onSuccess={(customer) => { handleSelectCustomer(customer); setShowCreateCustomerModal(false); }} /> { setShowCustomerModal(false); setCustomerSearchTerm(""); setCustomerSearchResults([]); }} onSearchChange={setCustomerSearchTerm} onSelectCustomer={handleSelectCustomer} /> setShowInfoModal(false)} /> setShowConfirmDialog(false)} confirmText={ confirmDialog.type === "delete" ? "Excluir" : confirmDialog.type === "warning" && confirmDialog.title === "Desconto sobre item de venda" ? "OK" : "Confirmar" } cancelText="Cancelar" /> setShowDeliveryTaxModal(false)} onSelect={handleSelectDeliveryTax} deliveryTaxOptions={deliveryTaxOptions} isLoading={isLoadingDeliveryTax} /> setShowDiscountModal(false)} onConfirm={handleConfirmDiscount} orderValue={cart .filter((item) => !item.promotion || item.promotion === 0) .reduce((acc, item) => acc + item.price * item.quantity, 0)} profit={cart .filter((item) => !item.promotion || item.promotion === 0) .reduce((acc, item) => acc + (item.cost || 0) * item.quantity, 0)} isManager={authService.isManager()} /> setShowTaxErrorDialog(false)} onConfirm={() => setShowTaxErrorDialog(false)} type={taxErrorDialog.type || "error"} title={taxErrorDialog.title} message={taxErrorDialog.message} confirmText="OK" /> setShowDateValidationDialog(false)} onConfirm={() => setShowDateValidationDialog(false)} type="info" title="Data de Entrega" message="A data de previsão de entrega deve ser de no mínimo 3 dias (D+3) a partir de hoje. A data foi ajustada automaticamente." confirmText="OK" /> {/* Modal de Desconto do Item */} { setShowDiscountItemModal(false); setSelectedItemForDiscount(null); }} onConfirm={handleDiscountItemConfirm} /> {/* Modal de Edição de Produto */} {showProductEditModal && selectedItemForEdit && ( { setShowProductEditModal(false); setSelectedItemForEdit(null); }} onConfirm={async (updatedItem) => { // Atualizar item no carrinho try { const shoppingItem = shoppingService.productToShoppingItem({ ...updatedItem, quantity: updatedItem.quantity, }); shoppingItem.id = selectedItemForEdit.id; await shoppingService.updateQuantityItemShopping(shoppingItem); setShowProductEditModal(false); setSelectedItemForEdit(null); // Atualizar carrinho if (onCartUpdate) { onCartUpdate(); } else { window.location.reload(); } } catch (error: any) { console.error("Erro ao atualizar item:", error); setConfirmDialog({ type: "error", title: "Erro", message: error.message || "Não foi possível atualizar o item do carrinho.", onConfirm: () => { setShowConfirmDialog(false); }, }); setShowConfirmDialog(true); } }} /> )}
); }; export default CheckoutView;