700 lines
31 KiB
JavaScript
700 lines
31 KiB
JavaScript
import { useState, useMemo, useEffect, useCallback, useRef } from 'react';
|
|
import { useToast } from './useToast';
|
|
import { workspaceReceitasService } from '@/services/workspaceReceitasService';
|
|
import { workspaceEntradasPlanejadasService } from '@/services/workspaceEntradasPlanejadasService';
|
|
import { extratoService } from '@/services/extratoService';
|
|
import { parseCurrency } from '@/utils/dateUtils';
|
|
|
|
// Mock Transactions (Entradas focus) com datas padronizadas
|
|
const MOCK_TRANSACTIONS = Array.from({ length: 50 }, (_, i) => {
|
|
const day = String((i % 30) + 1).padStart(2, '0');
|
|
return {
|
|
id: `REC-${2000 + i}`,
|
|
dataEntrada: `2026-01-${day}`,
|
|
descricao: i % 2 === 0 ? `Pagamento Boleto ${i}` : `Serviço Consultoria ${i}`,
|
|
cliente: i % 3 === 0 ? 'Cliente A LTDA' : (i % 3 === 1 ? 'B Services' : 'Cliente C Comércio'),
|
|
categoria: ['Venda de Produto', 'Serviços', 'Receita Financeira', 'Outros'][i % 4],
|
|
valor: (Math.random() * 3000 + 500).toFixed(2),
|
|
status: i % 4 === 0 ? 'Pendente' : 'Recebido'
|
|
};
|
|
}).map(item => ({
|
|
...item,
|
|
valor: parseFloat(item.valor)
|
|
}));
|
|
|
|
// Mock Clients com dados mais realistas
|
|
const MOCK_CLIENTS = [
|
|
{
|
|
id: 1,
|
|
nome: 'Sr. Daivid Alves',
|
|
nome_exibicao: 'Novas industrias',
|
|
email: 'Novas.industrias@gmail.com',
|
|
status_serv: 'Ativo',
|
|
telefone: '21995882003',
|
|
caixinha: '1',
|
|
cpf_cnpj: '12.345.678/0001-90',
|
|
endereco: 'Rua 1 lote 15 do vilar gunabara',
|
|
bairro: 'Vilar Gunabara',
|
|
cidade: 'Rio de Janeiro',
|
|
uf: 'RJ',
|
|
cep: '2214214-650',
|
|
dominio: 'novasindustrias.com.br',
|
|
tipo_pessoa: 'JURIDICA',
|
|
data_vencimento: 30,
|
|
obs: 'Cliente premium desde 2020. Excelente histórico de pagamento. Contato preferencial via e-mail.',
|
|
valor_servico: 45840940.00,
|
|
contasReceber: 45840940.00,
|
|
creditosNaoUtilizados: 46.20
|
|
},
|
|
{
|
|
id: 2,
|
|
nome: 'Tech Solutions Ltda',
|
|
nome_exibicao: 'Tech Solutions',
|
|
email: 'contato@techsolutions.com.br',
|
|
status_serv: 'Ativo',
|
|
telefone: '(11) 3456-7890',
|
|
caixinha: '2',
|
|
cpf_cnpj: '23.456.789/0001-12',
|
|
endereco: 'Av. Paulista, 1578',
|
|
bairro: 'Bela Vista',
|
|
cidade: 'São Paulo',
|
|
uf: 'SP',
|
|
cep: '01310-200',
|
|
dominio: 'techsolutions.com.br',
|
|
tipo_pessoa: 'JURIDICA',
|
|
data_vencimento: 15,
|
|
obs: 'Empresa de tecnologia com foco em soluções corporativas. Cliente desde 2019.',
|
|
valor_servico: 125000.00,
|
|
contasReceber: 125000.00,
|
|
creditosNaoUtilizados: 0.00
|
|
},
|
|
{
|
|
id: 3,
|
|
nome: 'Logística Rápida S.A.',
|
|
nome_exibicao: 'Logística Rápida',
|
|
email: 'financeiro@logisticarapida.com.br',
|
|
status_serv: 'Ativo',
|
|
telefone: '(21) 2345-6789',
|
|
caixinha: '3',
|
|
cpf_cnpj: '34.567.890/0001-23',
|
|
endereco: 'Rua do Comércio, 450',
|
|
bairro: 'Centro',
|
|
cidade: 'Rio de Janeiro',
|
|
uf: 'RJ',
|
|
cep: '20010-000',
|
|
dominio: 'logisticarapida.com.br',
|
|
tipo_pessoa: 'JURIDICA',
|
|
data_vencimento: 20,
|
|
obs: 'Empresa de logística com operações em todo Brasil. Contrato anual renovado.',
|
|
valor_servico: 250000.00,
|
|
contasReceber: 250000.00,
|
|
creditosNaoUtilizados: 150.00
|
|
},
|
|
{
|
|
id: 4,
|
|
nome: 'Indústrias Metalúrgicas ABC',
|
|
nome_exibicao: 'Metalúrgicas ABC',
|
|
email: 'contato@metalurgicasabc.com.br',
|
|
status_serv: 'Ativo',
|
|
telefone: '(11) 9876-5432',
|
|
caixinha: '4',
|
|
cpf_cnpj: '45.678.901/0001-34',
|
|
endereco: 'Rodovia dos Bandeirantes, Km 45',
|
|
bairro: 'Distrito Industrial',
|
|
cidade: 'Campinas',
|
|
uf: 'SP',
|
|
cep: '13000-000',
|
|
dominio: 'metalurgicasabc.com.br',
|
|
tipo_pessoa: 'JURIDICA',
|
|
data_vencimento: 30,
|
|
obs: 'Grande indústria metalúrgica. Cliente estratégico com volume alto de transações.',
|
|
valor_servico: 890000.20,
|
|
contasReceber: 890000.20,
|
|
creditosNaoUtilizados: 0.00
|
|
},
|
|
{
|
|
id: 5,
|
|
nome: 'B Services',
|
|
nome_exibicao: 'B Services',
|
|
email: 'admin@cliente-b.br',
|
|
status_serv: 'Inativo',
|
|
telefone: '(21) 8888-8888',
|
|
caixinha: '5',
|
|
cpf_cnpj: '98.765.432/0001-11',
|
|
endereco: 'Av. Brasil, 500',
|
|
bairro: 'Copacabana',
|
|
cidade: 'Rio de Janeiro',
|
|
uf: 'RJ',
|
|
cep: '20000-000',
|
|
dominio: 'bservices.com',
|
|
tipo_pessoa: 'JURIDICA',
|
|
data_vencimento: 15,
|
|
obs: 'Aguardando renovação de contrato. Cliente em análise de crédito.',
|
|
valor_servico: 750.00,
|
|
contasReceber: 750.00,
|
|
creditosNaoUtilizados: 0.00
|
|
},
|
|
];
|
|
|
|
// Mock Services com mais opções
|
|
const MOCK_SERVICES = [
|
|
{ idservicos: 1, servico: 'Suporte Técnico Premium', valor: 750.00, descricao: 'Suporte técnico 24/7 com SLA garantido' },
|
|
{ idservicos: 2, servico: 'VPS Custom', valor: 300.00, descricao: 'Servidor virtual personalizado com recursos dedicados' },
|
|
{ idservicos: 3, servico: 'Hospedagem Cloud', valor: 150.00, descricao: 'Hospedagem em nuvem com alta disponibilidade' },
|
|
{ idservicos: 4, servico: 'Consultoria em TI', valor: 2000.00, descricao: 'Consultoria especializada em transformação digital' },
|
|
{ idservicos: 5, servico: 'Desenvolvimento Custom', valor: 5000.00, descricao: 'Desenvolvimento de software sob medida' },
|
|
{ idservicos: 6, servico: 'Backup Automatizado', valor: 100.00, descricao: 'Sistema de backup automático diário' },
|
|
];
|
|
|
|
// Mock Boletos com dados completos conforme imagem
|
|
const MOCK_BOLETOS = [
|
|
{ id: 1, pagador: 'Grupo Pralog', emitido: '2026-01-05', vencimento: '2026-01-10', tipoCobranca: 'SIMPLES', valorNominal: 4800.00, totalRecebido: 4800.00, status: 'RECEBIDO', banco: 'Banco do Brasil', descricao: 'Pagamento de serviços', numero: 'BOL-000001' },
|
|
{ id: 2, pagador: 'ENSEG Participações', emitido: '2026-01-08', vencimento: '2026-01-15', tipoCobranca: 'RECORRENTE', valorNominal: 3500.00, totalRecebido: 3500.00, status: 'RECEBIDO', banco: 'Itaú', descricao: 'Mensalidade recorrente', numero: 'BOL-000002' },
|
|
{ id: 3, pagador: 'Tech Solutions Ltda', emitido: '2026-01-10', vencimento: '2026-01-20', tipoCobranca: 'SIMPLES', valorNominal: 1500.00, totalRecebido: 0.00, status: 'VENCIDO', banco: 'Banco do Brasil', descricao: 'Consultoria em TI', numero: 'BOL-000003' },
|
|
{ id: 4, pagador: 'Logística Rápida S.A.', emitido: '2026-01-12', vencimento: '2026-01-25', tipoCobranca: 'RECORRENTE', valorNominal: 12300.00, totalRecebido: 12300.00, status: 'RECEBIDO', banco: 'Itaú', descricao: 'Mensalidade de serviços', numero: 'BOL-000004' },
|
|
{ id: 5, pagador: 'Indústrias Metalúrgicas ABC', emitido: '2026-01-15', vencimento: '2026-01-30', tipoCobranca: 'SIMPLES', valorNominal: 8900.20, totalRecebido: 0.00, status: 'VENCIDO', banco: 'Bradesco', descricao: 'Desenvolvimento Custom', numero: 'BOL-000005' },
|
|
{ id: 6, pagador: 'B Services', emitido: '2026-01-18', vencimento: '2026-02-05', tipoCobranca: 'SIMPLES', valorNominal: 1200.00, totalRecebido: 0.00, status: 'PENDENTE', banco: 'Banco do Brasil', descricao: 'Renovação de contrato', numero: 'BOL-000006' },
|
|
{ id: 7, pagador: 'Sr. Daivid Alves', emitido: '2026-01-20', vencimento: '2026-02-10', tipoCobranca: 'RECORRENTE', valorNominal: 2500.00, totalRecebido: 2500.00, status: 'MARCADO_RECEBIDO', banco: 'Banco do Brasil', descricao: 'Pagamento marcado', numero: 'BOL-000007' },
|
|
{ id: 8, pagador: 'Tech Solutions Ltda', emitido: '2026-01-22', vencimento: '2026-02-15', tipoCobranca: 'SIMPLES', valorNominal: 500.00, totalRecebido: 0.00, status: 'CANCELADO', banco: 'Santander', descricao: 'Serviço cancelado', numero: 'BOL-000008' },
|
|
{ id: 9, pagador: 'Logística Rápida S.A.', emitido: '2026-01-25', vencimento: '2026-02-20', tipoCobranca: 'PIX', valorNominal: 3565.80, totalRecebido: 3565.80, status: 'PIX', banco: 'Itaú', descricao: 'Pagamento via Pix', numero: 'BOL-000009' },
|
|
{ id: 10, pagador: 'Grupo Pralog', emitido: '2026-01-28', vencimento: '2026-02-25', tipoCobranca: 'PIX', valorNominal: 4800.00, totalRecebido: 4800.00, status: 'PIX', banco: 'Banco do Brasil', descricao: 'Pagamento Pix', numero: 'BOL-000010' },
|
|
// Adicionar mais boletos para melhor visualização
|
|
{ id: 11, pagador: 'ENSEG Participações', emitido: '2026-01-03', vencimento: '2026-01-08', tipoCobranca: 'SIMPLES', valorNominal: 8200.00, totalRecebido: 0.00, status: 'VENCIDO', banco: 'Bradesco', descricao: 'Pagamento em atraso', numero: 'BOL-000011' },
|
|
{ id: 12, pagador: 'Indústrias Metalúrgicas ABC', emitido: '2026-01-06', vencimento: '2026-01-12', tipoCobranca: 'RECORRENTE', valorNominal: 6300.00, totalRecebido: 0.00, status: 'VENCIDO', banco: 'Itaú', descricao: 'Mensalidade atrasada', numero: 'BOL-000012' },
|
|
{ id: 13, pagador: 'Grupo Pralog', emitido: '2026-01-07', vencimento: '2026-01-14', tipoCobranca: 'SIMPLES', valorNominal: 2400.00, totalRecebido: 0.00, status: 'CANCELADO', banco: 'Santander', descricao: 'Boleto cancelado', numero: 'BOL-000013' },
|
|
{ id: 14, pagador: 'Tech Solutions Ltda', emitido: '2026-01-09', vencimento: '2026-01-18', tipoCobranca: 'RECORRENTE', valorNominal: 5500.00, totalRecebido: 5500.00, status: 'RECEBIDO', banco: 'Banco do Brasil', descricao: 'Pagamento recebido', numero: 'BOL-000014' },
|
|
{ id: 15, pagador: 'Logística Rápida S.A.', emitido: '2026-01-11', vencimento: '2026-01-22', tipoCobranca: 'PIX', valorNominal: 1800.00, totalRecebido: 1800.00, status: 'PIX', banco: 'Itaú', descricao: 'Pix recebido', numero: 'BOL-000015' },
|
|
];
|
|
|
|
export const useContasReceber = (defaultView = 'default') => {
|
|
const toast = useToast();
|
|
|
|
// Normalização de alias
|
|
const normalizeSubView = (view) => {
|
|
if (!view) return 'default';
|
|
if (view === 'boletos-pendentes') return 'boletos';
|
|
return view;
|
|
};
|
|
|
|
const initialSubView = useMemo(() => normalizeSubView(defaultView), [defaultView]);
|
|
|
|
const [activeSubView, setActiveSubView] = useState(initialSubView); // 'default' | 'servicos' | 'clientes' | 'entradas-planejadas' | 'boletos'
|
|
|
|
// Atualizar view quando a prop mudar
|
|
useEffect(() => {
|
|
if (defaultView) {
|
|
setActiveSubView(normalizeSubView(defaultView));
|
|
}
|
|
}, [defaultView]);
|
|
const [transactions, setTransactions] = useState([]);
|
|
const [cruzamentoLoading, setCruzamentoLoading] = useState(true);
|
|
const [clients, setClients] = useState([]);
|
|
const [services, setServices] = useState([]);
|
|
const [entradasPlanejadas, setEntradasPlanejadas] = useState([]);
|
|
const [boletos, setBoletos] = useState(MOCK_BOLETOS || []);
|
|
const [isLoadingServicos, setIsLoadingServicos] = useState(false);
|
|
const [isLoadingClients, setIsLoadingClients] = useState(false);
|
|
const [isLoadingEntradasPlanejadas, setIsLoadingEntradasPlanejadas] = useState(false);
|
|
const [isLoadingItens, setIsLoadingItens] = useState(false);
|
|
const [itensEntradaSelecionada, setItensEntradaSelecionada] = useState([]);
|
|
const [categorias, setCategorias] = useState([]);
|
|
const [caixas, setCaixas] = useState([]);
|
|
|
|
// Cruzamento: alimentado por /api/extrato/apresentar (tipoOperacao C). Planejado zerado.
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
setCruzamentoLoading(true);
|
|
extratoService
|
|
.fetchExtrato()
|
|
.then((raw) => {
|
|
if (cancelled) return;
|
|
const list = Array.isArray(raw) ? raw : [];
|
|
const receitas = list
|
|
.filter((item) => item.tipoOperacao === 'C')
|
|
.map((item) => ({
|
|
id: item.idextrato ?? item.id,
|
|
idextrato: item.idextrato,
|
|
dataEntrada: item.dataEntrada || '',
|
|
descricao: item.descricao || item.titulo || '',
|
|
valor: Math.abs(parseCurrency(item.valor || 0)),
|
|
categoria: item.categoria ?? 'Sem Categoria',
|
|
caixinha: item.caixinha ?? '',
|
|
status: 'Recebido',
|
|
beneficiario_pagador: item.beneficiario_pagador,
|
|
tipoTransacao: item.tipoTransacao
|
|
}));
|
|
setTransactions(receitas);
|
|
})
|
|
.catch((err) => {
|
|
if (!cancelled) {
|
|
console.error('[useContasReceber] Erro ao carregar extrato para cruzamento:', err);
|
|
setTransactions([]);
|
|
}
|
|
})
|
|
.finally(() => {
|
|
if (!cancelled) setCruzamentoLoading(false);
|
|
});
|
|
return () => { cancelled = true; };
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
Promise.all([extratoService.fetchCategorias(), extratoService.fetchCaixinhas()])
|
|
.then(([cats, cxs]) => {
|
|
if (cancelled) return;
|
|
setCategorias(Array.isArray(cats) ? cats : []);
|
|
setCaixas(Array.isArray(cxs) ? cxs : []);
|
|
})
|
|
.catch(() => {
|
|
if (!cancelled) {
|
|
setCategorias([]);
|
|
setCaixas([]);
|
|
}
|
|
});
|
|
return () => { cancelled = true; };
|
|
}, []);
|
|
|
|
/** Carrega serviços do backend (GET /servicos/list ou /servicos/list?tipo=...). */
|
|
const loadServicos = useCallback(async (tipo) => {
|
|
setIsLoadingServicos(true);
|
|
try {
|
|
const data = await workspaceReceitasService.fetchServicos(tipo);
|
|
const servicesList = Array.isArray(data) ? data : [];
|
|
setServices(servicesList);
|
|
return servicesList;
|
|
} catch (err) {
|
|
console.warn('[useContasReceber] Erro ao carregar serviços:', err);
|
|
toast.error('Erro ao carregar serviços');
|
|
setServices([]);
|
|
return [];
|
|
} finally {
|
|
setIsLoadingServicos(false);
|
|
}
|
|
}, [toast]);
|
|
|
|
/** Carrega clientes do backend (GET /empresas_financeiro). */
|
|
const loadClients = useCallback(async () => {
|
|
setIsLoadingClients(true);
|
|
try {
|
|
const data = await workspaceReceitasService.fetchClientes();
|
|
setClients(Array.isArray(data) ? data : []);
|
|
} catch (err) {
|
|
console.warn('[useContasReceber] Erro ao carregar clientes:', err);
|
|
toast.error('Erro ao carregar clientes');
|
|
setClients([]);
|
|
} finally {
|
|
setIsLoadingClients(false);
|
|
}
|
|
}, [toast]);
|
|
|
|
/** Carrega entradas planejadas do backend (GET /empresas_planejadas). */
|
|
const loadEntradasPlanejadas = useCallback(async () => {
|
|
setIsLoadingEntradasPlanejadas(true);
|
|
try {
|
|
const data = await workspaceEntradasPlanejadasService.fetchEntradasPlanejadas();
|
|
setEntradasPlanejadas(Array.isArray(data) ? data : []);
|
|
} catch (err) {
|
|
console.warn('[useContasReceber] Erro ao carregar entradas planejadas:', err);
|
|
toast.error('Erro ao carregar entradas planejadas');
|
|
setEntradasPlanejadas([]);
|
|
} finally {
|
|
setIsLoadingEntradasPlanejadas(false);
|
|
}
|
|
}, [toast]);
|
|
|
|
// Executa uma única vez no mount; ref evita reexecução mesmo com Strict Mode ou dependências instáveis
|
|
const didLoadReceitas = useRef(false);
|
|
useEffect(() => {
|
|
if (didLoadReceitas.current) return;
|
|
didLoadReceitas.current = true;
|
|
loadServicos();
|
|
loadClients();
|
|
loadEntradasPlanejadas();
|
|
}, [loadServicos, loadClients, loadEntradasPlanejadas]);
|
|
|
|
// Derived States seguros
|
|
const totalRecebido = useMemo(() => {
|
|
if (!Array.isArray(transactions)) return 0;
|
|
return transactions
|
|
.filter(t => t?.status === 'Recebido' || t?.status === 'Liquidado' || t?.status === 'Marcado como Recebido')
|
|
.reduce((acc, t) => acc + (t?.valor || 0), 0);
|
|
}, [transactions]);
|
|
|
|
// Cruzamento: lado planejado alimentado por entradasPlanejadas; executado por extrato (transactions).
|
|
const totalPendente = useMemo(() => {
|
|
if (!Array.isArray(entradasPlanejadas)) return 0;
|
|
return entradasPlanejadas.reduce((acc, item) => acc + (Number(item.total || 0)), 0);
|
|
}, [entradasPlanejadas]);
|
|
|
|
const totalGeral = totalRecebido + totalPendente;
|
|
|
|
// Actions para Serviços
|
|
const parseValor = useCallback((v) => {
|
|
if (v == null || v === '') return NaN;
|
|
const n = typeof v === 'number' ? v : Number(String(v).replace(',', '.'));
|
|
return Number.isFinite(n) ? n : NaN;
|
|
}, []);
|
|
|
|
const formatDataEnvio = useCallback((dateStr) => {
|
|
if (!dateStr) return '';
|
|
const d = new Date(dateStr);
|
|
if (Number.isNaN(d.getTime())) return '';
|
|
const y = d.getFullYear();
|
|
const m = d.getMonth() + 1;
|
|
const day = d.getDate();
|
|
return `${y}-${m}-${day}`;
|
|
}, []);
|
|
|
|
const createService = useCallback(async (serviceData) => {
|
|
if (!serviceData.servico?.trim()) {
|
|
toast.error('Nome do serviço é obrigatório', 'Campos Obrigatórios');
|
|
return null;
|
|
}
|
|
const valor = parseValor(serviceData.valor);
|
|
if (valor <= 0 || !Number.isFinite(valor)) {
|
|
toast.error('Valor do serviço deve ser maior que zero', 'Validação');
|
|
return null;
|
|
}
|
|
try {
|
|
const payload = {
|
|
servico: serviceData.servico.trim(),
|
|
valor,
|
|
imagem: serviceData.imagem ?? '',
|
|
tipo_servico: serviceData.tipo_servico || 'Serviço'
|
|
};
|
|
await workspaceReceitasService.createServico(payload);
|
|
await loadServicos();
|
|
toast.success('Serviço criado com sucesso!', 'Sucesso');
|
|
return true;
|
|
} catch (err) {
|
|
console.warn('[useContasReceber] Erro ao criar serviço:', err);
|
|
toast.error('Erro ao criar serviço. Tente novamente.');
|
|
return null;
|
|
}
|
|
}, [loadServicos, toast, parseValor]);
|
|
|
|
const updateService = useCallback(async (id, serviceData) => {
|
|
if (!serviceData.servico?.trim()) {
|
|
toast.error('Nome do serviço é obrigatório', 'Campos Obrigatórios');
|
|
return null;
|
|
}
|
|
const valor = parseValor(serviceData.valor);
|
|
if (valor <= 0 || !Number.isFinite(valor)) {
|
|
toast.error('Valor do serviço deve ser maior que zero', 'Validação');
|
|
return null;
|
|
}
|
|
try {
|
|
const payload = {
|
|
idservicos: Number(id),
|
|
servico: serviceData.servico.trim(),
|
|
valor,
|
|
imagem: serviceData.imagem ?? '',
|
|
tipo_servico: serviceData.tipo_servico || 'Serviço'
|
|
};
|
|
await workspaceReceitasService.editServico(payload);
|
|
await loadServicos();
|
|
toast.success('Serviço atualizado com sucesso!', 'Sucesso');
|
|
return true;
|
|
} catch (err) {
|
|
console.warn('[useContasReceber] Erro ao editar serviço:', err);
|
|
toast.error('Erro ao editar serviço. Tente novamente.');
|
|
return null;
|
|
}
|
|
}, [loadServicos, toast, parseValor]);
|
|
|
|
const deleteService = useCallback(async (id) => {
|
|
try {
|
|
await workspaceReceitasService.deleteServico({ idservicos: Number(id) });
|
|
await loadServicos();
|
|
toast.success('Serviço excluído com sucesso!', 'Sucesso');
|
|
return true;
|
|
} catch (err) {
|
|
console.warn('[useContasReceber] Erro ao excluir serviço:', err);
|
|
toast.error('Erro ao excluir serviço. Tente novamente.');
|
|
return null;
|
|
}
|
|
}, [loadServicos, toast]);
|
|
|
|
// Actions para Clientes
|
|
const createClient = useCallback(async (clientData) => {
|
|
const camposObrigatorios = [];
|
|
if (!clientData.nome?.trim()) camposObrigatorios.push('Nome');
|
|
if (!clientData.email?.trim()) camposObrigatorios.push('Email');
|
|
if (!clientData.cpf_cnpj?.trim()) camposObrigatorios.push('CPF/CNPJ');
|
|
|
|
if (camposObrigatorios.length > 0) {
|
|
toast.notifyFields(camposObrigatorios);
|
|
return null;
|
|
}
|
|
|
|
setIsLoadingClients(true);
|
|
try {
|
|
const response = await workspaceReceitasService.createClient(clientData);
|
|
await loadClients();
|
|
toast.success('Cliente criado com sucesso!', 'Sucesso');
|
|
return response;
|
|
} catch (err) {
|
|
console.warn('[useContasReceber] Erro ao criar cliente:', err);
|
|
toast.handleBackendError(err);
|
|
return null;
|
|
} finally {
|
|
setIsLoadingClients(false);
|
|
}
|
|
}, [loadClients, toast]);
|
|
|
|
const updateClient = useCallback(async (id, clientData) => {
|
|
const camposObrigatorios = [];
|
|
if (!clientData.nome?.trim()) camposObrigatorios.push('Nome');
|
|
if (!clientData.email?.trim()) camposObrigatorios.push('Email');
|
|
if (!clientData.cpf_cnpj?.trim()) camposObrigatorios.push('CPF/CNPJ');
|
|
|
|
if (camposObrigatorios.length > 0) {
|
|
toast.notifyFields(camposObrigatorios);
|
|
return false;
|
|
}
|
|
|
|
setIsLoadingClients(true);
|
|
try {
|
|
await workspaceReceitasService.updateClient({ ...clientData, idempresa: id });
|
|
await loadClients();
|
|
toast.success('Cliente atualizado com sucesso!', 'Sucesso');
|
|
return true;
|
|
} catch (err) {
|
|
console.warn('[useContasReceber] Erro ao atualizar cliente:', err);
|
|
toast.handleBackendError(err);
|
|
return false;
|
|
} finally {
|
|
setIsLoadingClients(false);
|
|
}
|
|
}, [loadClients, toast]);
|
|
|
|
const updateClientStatus = useCallback(async (id, status) => {
|
|
setIsLoadingClients(true);
|
|
try {
|
|
await workspaceReceitasService.updateClientStatus({ idempresa: id, status, data_envio: formatDataEnvio(new Date()) });
|
|
await loadClients();
|
|
toast.success(`Status do cliente alterado para ${status}!`, 'Sucesso');
|
|
return true;
|
|
} catch (err) {
|
|
console.warn('[useContasReceber] Erro ao alterar status do cliente:', err);
|
|
toast.handleBackendError(err);
|
|
return false;
|
|
} finally {
|
|
setIsLoadingClients(false);
|
|
}
|
|
}, [loadClients, toast, formatDataEnvio]);
|
|
|
|
const deleteClient = useCallback(async (id) => {
|
|
// Nota: O service não tem deleteClient explicitamente, geralmente é feito via status Inativo
|
|
// Mas podemos implementar se houver rota. Como não vi rota de delete no service, usarei updateStatus
|
|
return updateClientStatus(id, 'Inativo');
|
|
}, [updateClientStatus]);
|
|
|
|
const loadItensEntradaPlanejada = useCallback(async (idEntrada) => {
|
|
if (!idEntrada) return;
|
|
setIsLoadingItens(true);
|
|
try {
|
|
const itens = await workspaceEntradasPlanejadasService.fetchItensEntradaPlanejada(idEntrada);
|
|
setItensEntradaSelecionada(itens);
|
|
return itens;
|
|
} catch (err) {
|
|
console.warn('[useContasReceber] Erro ao carregar itens da entrada:', err);
|
|
toast.error('Erro ao carregar itens. Tente novamente.');
|
|
return [];
|
|
} finally {
|
|
setIsLoadingItens(false);
|
|
}
|
|
}, [toast]);
|
|
|
|
// Actions para Entradas Planejadas
|
|
const createEntradaPlanejada = useCallback(async (entradaData) => {
|
|
const camposObrigatorios = [];
|
|
if (!entradaData.dataEstimativa) camposObrigatorios.push('Data Estimativa');
|
|
if (!entradaData.cliente?.trim()) camposObrigatorios.push('Cliente');
|
|
if (!entradaData.vendedor?.trim()) camposObrigatorios.push('Vendedor');
|
|
if (!entradaData.itens || entradaData.itens.length === 0) camposObrigatorios.push('Itens');
|
|
|
|
if (camposObrigatorios.length > 0) {
|
|
toast.notifyFields(camposObrigatorios);
|
|
return null;
|
|
}
|
|
|
|
setIsLoadingEntradasPlanejadas(true);
|
|
try {
|
|
const { itens, ...parentData } = entradaData;
|
|
const response = await workspaceEntradasPlanejadasService.createEntradaPlanejada(parentData);
|
|
let idEntrada = response?.id || response?.idempresa || response?.identradas_planejadas || response?.Base_Dados_API?.[0]?.id || (response?.Base_Dados_API && response.Base_Dados_API.id);
|
|
|
|
if (!idEntrada) {
|
|
console.log('[useContasReceber] ID não retornado, consultando listagem para capturar ID...');
|
|
const list = await workspaceEntradasPlanejadasService.fetchEntradasPlanejadas();
|
|
const matched = list.find(e =>
|
|
String(e.numeroReferencia) === String(parentData.numeroReferencia) &&
|
|
e.cliente === parentData.cliente
|
|
);
|
|
idEntrada = matched?.id || matched?.idempresa || matched?.identradas_planejadas;
|
|
}
|
|
|
|
if (!idEntrada) {
|
|
console.warn('[useContasReceber] ID da entrada não encontrado:', response);
|
|
await loadEntradasPlanejadas();
|
|
toast.error('Entrada criada, mas houve um problema ao vincular itens.');
|
|
return false;
|
|
}
|
|
|
|
const itemPromises = itens.map(item =>
|
|
workspaceEntradasPlanejadasService.createItemEntradaPlanejada({
|
|
...item,
|
|
idEntrada: idEntrada
|
|
})
|
|
);
|
|
|
|
await Promise.all(itemPromises);
|
|
await loadEntradasPlanejadas();
|
|
toast.success('Entrada e itens criados com sucesso!', 'Sucesso');
|
|
return true;
|
|
} catch (err) {
|
|
console.warn('[useContasReceber] Erro ao criar entrada planejada:', err);
|
|
toast.error('Erro ao criar entrada planejada.');
|
|
return false;
|
|
} finally {
|
|
setIsLoadingEntradasPlanejadas(false);
|
|
}
|
|
}, [loadEntradasPlanejadas, toast]);
|
|
|
|
const updateEntradaPlanejada = useCallback(async (id, entradaData) => {
|
|
const camposObrigatorios = [];
|
|
if (!entradaData.dataEstimativa) camposObrigatorios.push('Data Estimativa');
|
|
if (!entradaData.cliente?.trim()) camposObrigatorios.push('Cliente');
|
|
if (!entradaData.vendedor?.trim()) camposObrigatorios.push('Vendedor');
|
|
if (!entradaData.itens || entradaData.itens.length === 0) camposObrigatorios.push('Itens');
|
|
|
|
if (camposObrigatorios.length > 0) {
|
|
toast.notifyFields(camposObrigatorios);
|
|
return false;
|
|
}
|
|
|
|
setIsLoadingEntradasPlanejadas(true);
|
|
try {
|
|
const { itens, ...parentData } = entradaData;
|
|
const idempresa = parentData.idempresa || id;
|
|
await workspaceEntradasPlanejadasService.updateEntradaPlanejada(idempresa, parentData);
|
|
|
|
const itemPromises = itens.map(item => {
|
|
const idItem = item.identradas_planejadas_itens || item.id;
|
|
if (idItem && typeof idItem === 'number') {
|
|
return workspaceEntradasPlanejadasService.updateItemEntradaPlanejada(idItem, {
|
|
...item,
|
|
idEntrada: idempresa
|
|
});
|
|
} else {
|
|
return workspaceEntradasPlanejadasService.createItemEntradaPlanejada({
|
|
...item,
|
|
idEntrada: idempresa
|
|
});
|
|
}
|
|
});
|
|
|
|
await Promise.all(itemPromises);
|
|
await loadEntradasPlanejadas();
|
|
toast.success('Entrada planejada atualizada com sucesso!', 'Sucesso');
|
|
return true;
|
|
} catch (err) {
|
|
console.warn('[useContasReceber] Erro ao atualizar entrada planejada:', err);
|
|
toast.error('Erro ao atualizar entrada planejada.');
|
|
return false;
|
|
} finally {
|
|
setIsLoadingEntradasPlanejadas(false);
|
|
}
|
|
}, [loadEntradasPlanejadas, toast]);
|
|
|
|
const deleteEntradaPlanejada = useCallback(async (id) => {
|
|
try {
|
|
await workspaceEntradasPlanejadasService.deleteEntradaPlanejada(id);
|
|
await loadEntradasPlanejadas();
|
|
toast.success('Entrada planejada excluída com sucesso!', 'Sucesso');
|
|
return true;
|
|
} catch (err) {
|
|
console.warn('[useContasReceber] Erro ao excluir entrada planejada:', err);
|
|
toast.error('Erro ao excluir entrada planejada.');
|
|
return null;
|
|
}
|
|
}, [loadEntradasPlanejadas, toast]);
|
|
|
|
// Actions para Boletos
|
|
const downloadBoleto = useCallback((boletoId) => {
|
|
console.log('Download boleto:', boletoId);
|
|
}, []);
|
|
|
|
const sendBoleto = useCallback((boletoId) => {
|
|
console.log('Enviar boleto:', boletoId);
|
|
}, []);
|
|
|
|
const scheduleBoleto = useCallback((boletoId, newDate) => {
|
|
setBoletos(prev => prev.map(b => b.id === boletoId ? { ...b, dataVencimento: newDate } : b));
|
|
}, []);
|
|
|
|
const downloadFatura = useCallback((boletoId) => {
|
|
console.log('Download fatura:', boletoId);
|
|
}, []);
|
|
|
|
const actions = useMemo(() => ({
|
|
setActiveSubView,
|
|
loadServicos,
|
|
loadClients,
|
|
loadEntradasPlanejadas,
|
|
// Serviços
|
|
createService,
|
|
updateService,
|
|
deleteService,
|
|
// Clientes
|
|
createClient,
|
|
updateClient,
|
|
updateClientStatus,
|
|
deleteClient,
|
|
// Entradas Planejadas
|
|
createEntradaPlanejada,
|
|
updateEntradaPlanejada,
|
|
deleteEntradaPlanejada,
|
|
loadItensEntradaPlanejada,
|
|
setItensEntradaSelecionada,
|
|
// Boletos
|
|
downloadBoleto,
|
|
sendBoleto,
|
|
scheduleBoleto,
|
|
downloadFatura,
|
|
}), [
|
|
setActiveSubView, loadServicos, loadClients, loadEntradasPlanejadas,
|
|
createService, updateService, deleteService,
|
|
createClient, updateClient, updateClientStatus, deleteClient,
|
|
createEntradaPlanejada, updateEntradaPlanejada, deleteEntradaPlanejada,
|
|
loadItensEntradaPlanejada, setItensEntradaSelecionada,
|
|
downloadBoleto, sendBoleto, scheduleBoleto, downloadFatura
|
|
]);
|
|
|
|
return {
|
|
state: {
|
|
activeSubView,
|
|
transactions: transactions || [],
|
|
clients: clients || [],
|
|
services: services || [],
|
|
entradasPlanejadas: entradasPlanejadas || [],
|
|
itensEntradaSelecionada,
|
|
boletos: boletos || [],
|
|
isLoadingServicos,
|
|
isLoadingClients,
|
|
isLoadingEntradasPlanejadas,
|
|
isLoadingItens,
|
|
cruzamentoLoading,
|
|
kpis: {
|
|
totalRecebido,
|
|
totalPendente,
|
|
totalGeral
|
|
},
|
|
categorias: categorias || [],
|
|
caixas: caixas || []
|
|
},
|
|
actions
|
|
};
|
|
};
|