// 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 (
{fmtBRL(oc.valor_total)}