// Telas da Ordem de Compra: criação e detalhe. // Separado de Compras.jsx só por tamanho — a lógica de dados (criar, // enviar, cancelar) mora lá; aqui é formulário e apresentação. const OC_INPUT = { border: `1px solid ${DS.colors.border}`, borderRadius: DS.radius.sm, padding: '8px 10px', fontSize: 13, outline: 'none', fontFamily: 'inherit', width: '100%', }; const OC_TEXTAREA = { border: `1px solid ${DS.colors.border}`, borderRadius: DS.radius.sm, padding: '9px 12px', fontSize: 13, outline: 'none', fontFamily: 'inherit', resize: 'vertical', lineHeight: 1.5, }; const OC_ITEM_VAZIO = { descricao: '', qtd: '1', unidade: 'un', valor_unit: '' }; // "1.250,50" digitado vira 1250.5 — as pessoas escrevem no formato BR const ocParaNumero = (v) => Number(String(v ?? '').replace(/\./g, '').replace(',', '.')) || 0; // Caminho de volta: 1250.5 vira "1250,5" para o formulario de correcao // reabrir com virgula, e nao com ponto decimal (que o parser acima comeria). const ocParaTexto = (n) => (n === null || n === undefined || n === '' ? '' : String(n).replace('.', ',')); // Status em que a OC ainda pode ser corrigida. Confirmada e Recusada ja tiveram // resposta do fornecedor — e a Confirmada gerou conta a pagar no Financeiro —, // entao mexer no documento depois disso desencontraria os dois lados. const OC_EDITAVEL = ['Rascunho', 'Enviada']; // --------------------------------------------------------------------- // Formulario da Ordem de Compra — cria uma nova ou corrige uma existente // --------------------------------------------------------------------- function OCFormModal({ open, oc = null, fornecedores = [], events = [], onClose, onSubmit }) { const edicao = !!oc; const vazio = { fornecedor_id: '', event_id: '', descricao: '', data_servico: '', forma_pagamento: 'PIX', prazo_pagamento: '', observacoes: '', }; const [form, setForm] = React.useState(vazio); const [itens, setItens] = React.useState([{ ...OC_ITEM_VAZIO }]); const [erro, setErro] = React.useState(null); const [saving, setSaving] = React.useState(false); React.useEffect(() => { if (!open) return; setErro(null); setSaving(false); if (!oc) { setForm(vazio); setItens([{ ...OC_ITEM_VAZIO }]); return; } setForm({ fornecedor_id: oc.fornecedor_id ? String(oc.fornecedor_id) : '', event_id: oc.event_id ? String(oc.event_id) : '', descricao: oc.descricao || '', data_servico: oc.data_servico || '', forma_pagamento: oc.forma_pagamento || 'PIX', prazo_pagamento: oc.prazo_pagamento || '', observacoes: oc.observacoes || '', }); setItens((oc.itens || []).length ? oc.itens.map(it => ({ descricao: it.descricao || '', qtd: ocParaTexto(it.qtd), unidade: it.unidade || 'un', valor_unit: ocParaTexto(it.valor_unit), })) : [{ ...OC_ITEM_VAZIO }]); }, [open, oc?.id]); const fornecedor = fornecedores.find(f => String(f.id) === String(form.fornecedor_id)); const evento = events.find(e => String(e.id) === String(form.event_id)); // Só fornecedor ativo entra na lista, mas o da OC que esta sendo corrigida // entra de qualquer jeito: se ele foi desativado depois, sumir da lista // apagaria o fornecedor da OC sem ninguem perceber. const opcoesFornecedor = React.useMemo(() => { const ativos = fornecedores.filter(f => f.status === 'Ativo'); const atual = fornecedores.find(f => String(f.id) === String(form.fornecedor_id)); const lista = atual && !ativos.some(f => String(f.id) === String(atual.id)) ? [...ativos, atual] : ativos; return [{ value: '', label: 'Selecione…' }, ...lista.map(f => ({ value: String(f.id), label: f.status === 'Ativo' ? f.nome : `${f.nome} (inativo)` }))]; }, [fornecedores, form.fornecedor_id]); // Ao escolher o evento, já sugere a data do serviço — quase sempre é a mesma const escolherEvento = (v) => { const ev = events.find(e => String(e.id) === String(v)); setForm(f => ({ ...f, event_id: v, data_servico: f.data_servico || (ev?.event_date || '') })); }; const setItem = (i, campo, valor) => setItens(prev => prev.map((it, idx) => idx === i ? { ...it, [campo]: valor } : it)); const itensLimpos = itens .map(it => ({ descricao: (it.descricao || '').trim(), qtd: ocParaNumero(it.qtd), unidade: (it.unidade || 'un').trim(), valor_unit: ocParaNumero(it.valor_unit), })) .filter(it => it.descricao || it.valor_unit > 0); const total = somaItens(itensLimpos); const totalMudou = edicao && Math.abs(total - (Number(oc.valor_total) || 0)) > 0.001; const salvar = async () => { if (!fornecedor) { setErro('Escolha o fornecedor.'); return; } if (!itensLimpos.length) { setErro('Adicione pelo menos um item com descrição e valor.'); return; } if (total <= 0) { setErro('O valor total precisa ser maior que zero.'); return; } setSaving(true); setErro(null); await onSubmit({ fornecedor, evento: evento || null, descricao: form.descricao.trim(), itens: itensLimpos, data_servico: form.data_servico || null, forma_pagamento: form.forma_pagamento, prazo_pagamento: form.prazo_pagamento.trim(), observacoes: form.observacoes.trim(), }); setSaving(false); }; const semBanco = fornecedor && !fornecedor.banco && !fornecedor.pix_chave; return (
{edicao && oc.status === 'Enviada' && (
Esta OC já foi enviada. A correção aparece na hora no link do fornecedor, mas o PDF que ele recebeu continua o antigo — reenvie depois de salvar.
)}
({ value: String(e.id), label: `${e.name}${e.event_date ? ` — ${dataBR(e.event_date)}` : ''}` }))]} />
{semBanco && (
{fornecedor.nome} não tem dados bancários cadastrados. A OC pode ser emitida, mas o Financeiro vai receber a conta a pagar sem saber para onde pagar. Vale preencher em Fornecedores antes.
)} {evento && (
Cliente: {evento.client_name || '—'} Data: {dataBR(evento.event_date)} Local: {evento.location || '—'}
)}