Primeiro commit

This commit is contained in:
2025-10-14 14:04:17 -03:00
commit 33d8645eb4
109 changed files with 55424 additions and 0 deletions

635
server.js Normal file
View File

@@ -0,0 +1,635 @@
const express = require('express');
const cors = require('cors');
const path = require('path');
const multer = require('multer');
const sqlite3 = require('sqlite3').verbose();
const { v4: uuidv4 } = require('uuid');
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 5000;
// Middleware
app.use(cors());
app.use(express.json());
app.use('/uploads', express.static('uploads'));
app.use(express.static(path.join(__dirname, 'client/build')));
// Configuração do multer para upload de imagens
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
const uniqueName = `${Date.now()}-${uuidv4()}${path.extname(file.originalname)}`;
cb(null, uniqueName);
}
});
const upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Apenas imagens são permitidas!'), false);
}
},
limits: { fileSize: 5 * 1024 * 1024 } // 5MB
});
// Inicializar banco de dados
const db = new sqlite3.Database('./liberi_kids.db', (err) => {
if (err) {
console.error('Erro ao conectar com o banco de dados:', err.message);
} else {
console.log('Conectado ao banco de dados SQLite.');
initializeDatabase();
}
});
// Função para inicializar as tabelas
function initializeDatabase() {
// Tabela de produtos
db.run(`CREATE TABLE IF NOT EXISTS produtos (
id TEXT PRIMARY KEY,
id_produto TEXT,
marca TEXT NOT NULL,
nome TEXT NOT NULL,
estacao TEXT NOT NULL,
genero TEXT DEFAULT 'Unissex',
fornecedor_id TEXT,
valor_compra REAL NOT NULL,
valor_revenda REAL NOT NULL,
foto_principal_url TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (fornecedor_id) REFERENCES fornecedores (id)
)`);
// Tabela de variações de produtos (tamanho, cor, quantidade)
db.run(`CREATE TABLE IF NOT EXISTS produto_variacoes (
id TEXT PRIMARY KEY,
produto_id TEXT NOT NULL,
tamanho TEXT NOT NULL,
cor TEXT NOT NULL,
quantidade INTEGER NOT NULL DEFAULT 0,
foto_url TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (produto_id) REFERENCES produtos (id) ON DELETE CASCADE
)`);
// Tabela de clientes
db.run(`CREATE TABLE IF NOT EXISTS clientes (
id TEXT PRIMARY KEY,
nome_completo TEXT NOT NULL,
email TEXT,
telefone TEXT,
endereco TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`);
// Tabela de fornecedores
db.run(`CREATE TABLE IF NOT EXISTS fornecedores (
id TEXT PRIMARY KEY,
razao_social TEXT NOT NULL,
telefone TEXT,
whatsapp TEXT,
endereco TEXT,
email TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`);
// Tabela de tipos de despesas
db.run(`CREATE TABLE IF NOT EXISTS tipos_despesas (
id TEXT PRIMARY KEY,
nome TEXT NOT NULL UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`);
// Tabela de despesas
db.run(`CREATE TABLE IF NOT EXISTS despesas (
id TEXT PRIMARY KEY,
tipo_despesa_id TEXT NOT NULL,
fornecedor_id TEXT,
data DATE NOT NULL,
valor REAL NOT NULL,
descricao TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (tipo_despesa_id) REFERENCES tipos_despesas (id),
FOREIGN KEY (fornecedor_id) REFERENCES fornecedores (id)
)`);
// Tabela de vendas
db.run(`CREATE TABLE IF NOT EXISTS vendas (
id TEXT PRIMARY KEY,
cliente_id TEXT,
tipo_pagamento TEXT NOT NULL, -- 'vista' ou 'parcelado'
valor_total REAL NOT NULL,
desconto REAL DEFAULT 0,
parcelas INTEGER DEFAULT 1,
valor_parcela REAL,
data_venda DATE NOT NULL,
status TEXT DEFAULT 'concluida', -- 'concluida', 'cancelada'
observacoes TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (cliente_id) REFERENCES clientes (id)
)`);
// Tabela de itens da venda
db.run(`CREATE TABLE IF NOT EXISTS venda_itens (
id TEXT PRIMARY KEY,
venda_id TEXT NOT NULL,
produto_variacao_id TEXT NOT NULL,
quantidade INTEGER NOT NULL,
valor_unitario REAL NOT NULL,
valor_total REAL NOT NULL,
FOREIGN KEY (venda_id) REFERENCES vendas (id) ON DELETE CASCADE,
FOREIGN KEY (produto_variacao_id) REFERENCES produto_variacoes (id)
)`);
console.log('Tabelas do banco de dados inicializadas.');
}
// Rotas da API
// === PRODUTOS ===
app.get('/api/produtos', (req, res) => {
const query = `
SELECT p.*, f.razao_social as fornecedor_nome,
COUNT(pv.id) as total_variacoes,
SUM(pv.quantidade) as estoque_total
FROM produtos p
LEFT JOIN fornecedores f ON p.fornecedor_id = f.id
LEFT JOIN produto_variacoes pv ON p.id = pv.produto_id
GROUP BY p.id
ORDER BY p.created_at DESC
`;
db.all(query, [], (err, rows) => {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json(rows);
});
});
app.post('/api/produtos', upload.any(), (req, res) => {
console.log('Recebendo requisição para criar produto:', req.body);
console.log('Arquivos recebidos:', req.files ? req.files.length : 0);
const { id_produto, marca, nome, estacao, genero, fornecedor_id, valor_compra, valor_revenda, variacoes_data } = req.body;
const produtoId = uuidv4();
// Validações básicas
if (!marca || !nome || !estacao || !valor_compra || !valor_revenda) {
return res.status(400).json({ error: 'Campos obrigatórios não preenchidos' });
}
// Parse das variações
let variacoes = [];
try {
if (variacoes_data) {
variacoes = JSON.parse(variacoes_data);
}
} catch (error) {
console.error('Erro ao fazer parse das variações:', error);
return res.status(400).json({ error: 'Dados de variações inválidos' });
}
if (variacoes.length === 0) {
return res.status(400).json({ error: 'Pelo menos uma variação é obrigatória' });
}
// Iniciar transação
db.serialize(() => {
db.run('BEGIN TRANSACTION');
// Inserir produto
const produtoQuery = `INSERT INTO produtos (id, id_produto, marca, nome, estacao, genero, fornecedor_id, valor_compra, valor_revenda)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`;
const fornecedorIdFinal = fornecedor_id && fornecedor_id !== '' ? fornecedor_id : null;
db.run(produtoQuery, [produtoId, id_produto, marca, nome, estacao, genero || 'Unissex', fornecedorIdFinal, valor_compra, valor_revenda], function(err) {
if (err) {
console.error('Erro ao inserir produto:', err);
db.run('ROLLBACK');
return res.status(500).json({ error: err.message });
}
console.log('Produto inserido com sucesso, processando variações...');
// Processar variações e fotos
let variacoesProcessadas = 0;
const totalVariacoes = variacoes.length;
if (totalVariacoes === 0) {
db.run('COMMIT');
return res.json({ id: produtoId, message: 'Produto criado com sucesso!' });
}
variacoes.forEach((variacao, varIndex) => {
const variacaoId = uuidv4();
// Inserir variação
const variacaoQuery = `INSERT INTO produto_variacoes (id, produto_id, tamanho, cor, quantidade)
VALUES (?, ?, ?, ?, ?)`;
db.run(variacaoQuery, [variacaoId, produtoId, variacao.tamanho, variacao.cor, variacao.quantidade], function(err) {
if (err) {
console.error('Erro ao inserir variação:', err);
db.run('ROLLBACK');
return res.status(500).json({ error: err.message });
}
// Processar fotos desta variação
const fotosVariacao = req.files ? req.files.filter(file =>
file.fieldname.startsWith(`variacao_${varIndex}_foto_`)
) : [];
if (fotosVariacao.length > 0) {
// Usar a primeira foto como foto_url da variação
const primeiraFoto = fotosVariacao[0];
const fotoUrl = `/uploads/${primeiraFoto.filename}`;
// Atualizar variação com a primeira foto
db.run('UPDATE produto_variacoes SET foto_url = ? WHERE id = ?', [fotoUrl, variacaoId], function(err) {
if (err) {
console.error('Erro ao atualizar foto da variação:', err);
}
});
}
variacoesProcessadas++;
console.log(`Variação processada: ${variacoesProcessadas}/${totalVariacoes}`);
if (variacoesProcessadas === totalVariacoes) {
db.run('COMMIT');
console.log('Produto criado com sucesso!');
res.json({ id: produtoId, message: 'Produto criado com sucesso!' });
}
});
});
});
});
});
app.put('/api/produtos/:id', upload.single('foto_principal'), (req, res) => {
const { id } = req.params;
const { id_produto, marca, nome, estacao, genero, fornecedor_id, valor_compra, valor_revenda } = req.body;
let query, params;
if (req.file) {
const foto_principal_url = `/uploads/${req.file.filename}`;
query = `UPDATE produtos
SET id_produto = ?, marca = ?, nome = ?, estacao = ?, genero = ?, fornecedor_id = ?, valor_compra = ?, valor_revenda = ?, foto_principal_url = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?`;
params = [id_produto, marca, nome, estacao, genero || 'Unissex', fornecedor_id, valor_compra, valor_revenda, foto_principal_url, id];
} else {
query = `UPDATE produtos
SET id_produto = ?, marca = ?, nome = ?, estacao = ?, genero = ?, fornecedor_id = ?, valor_compra = ?, valor_revenda = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?`;
params = [id_produto, marca, nome, estacao, genero || 'Unissex', fornecedor_id, valor_compra, valor_revenda, id];
}
db.run(query, params, function(err) {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({ message: 'Produto atualizado com sucesso!' });
});
});
// === VARIAÇÕES DE PRODUTOS ===
app.get('/api/produtos/:id/variacoes', (req, res) => {
const { id } = req.params;
db.all('SELECT * FROM produto_variacoes WHERE produto_id = ? ORDER BY tamanho, cor', [id], (err, rows) => {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json(rows);
});
});
app.post('/api/produtos/:id/variacoes', upload.single('foto'), (req, res) => {
const { id } = req.params;
const { tamanho, cor, quantidade } = req.body;
const foto_url = req.file ? `/uploads/${req.file.filename}` : null;
const variacao_id = uuidv4();
const query = `INSERT INTO produto_variacoes (id, produto_id, tamanho, cor, quantidade, foto_url)
VALUES (?, ?, ?, ?, ?, ?)`;
db.run(query, [variacao_id, id, tamanho, cor, quantidade, foto_url], function(err) {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({ id: variacao_id, message: 'Variação adicionada com sucesso!' });
});
});
// === CLIENTES ===
app.get('/api/clientes', (req, res) => {
db.all('SELECT * FROM clientes ORDER BY nome_completo', [], (err, rows) => {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json(rows);
});
});
app.post('/api/clientes', (req, res) => {
const { nome_completo, email, telefone, endereco } = req.body;
const id = uuidv4();
const query = `INSERT INTO clientes (id, nome_completo, email, telefone, endereco)
VALUES (?, ?, ?, ?, ?)`;
db.run(query, [id, nome_completo, email, telefone, endereco], function(err) {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({ id, message: 'Cliente cadastrado com sucesso!' });
});
});
// === FORNECEDORES ===
app.get('/api/fornecedores', (req, res) => {
db.all('SELECT * FROM fornecedores ORDER BY razao_social', [], (err, rows) => {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json(rows);
});
});
app.post('/api/fornecedores', (req, res) => {
const { razao_social, telefone, whatsapp, endereco, email } = req.body;
const id = uuidv4();
const query = `INSERT INTO fornecedores (id, razao_social, telefone, whatsapp, endereco, email)
VALUES (?, ?, ?, ?, ?, ?)`;
db.run(query, [id, razao_social, telefone, whatsapp, endereco, email], function(err) {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({ id, message: 'Fornecedor cadastrado com sucesso!' });
});
});
// === TIPOS DE DESPESAS ===
app.get('/api/tipos-despesas', (req, res) => {
db.all('SELECT * FROM tipos_despesas ORDER BY nome', [], (err, rows) => {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json(rows);
});
});
app.post('/api/tipos-despesas', (req, res) => {
const { nome } = req.body;
const id = uuidv4();
const query = `INSERT INTO tipos_despesas (id, nome) VALUES (?, ?)`;
db.run(query, [id, nome], function(err) {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({ id, message: 'Tipo de despesa criado com sucesso!' });
});
});
// === DESPESAS ===
app.get('/api/despesas', (req, res) => {
const query = `
SELECT d.*, td.nome as tipo_nome, f.razao_social as fornecedor_nome
FROM despesas d
LEFT JOIN tipos_despesas td ON d.tipo_despesa_id = td.id
LEFT JOIN fornecedores f ON d.fornecedor_id = f.id
ORDER BY d.data DESC
`;
db.all(query, [], (err, rows) => {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json(rows);
});
});
app.post('/api/despesas', (req, res) => {
const { tipo_despesa_id, fornecedor_id, data, valor, descricao } = req.body;
const id = uuidv4();
const query = `INSERT INTO despesas (id, tipo_despesa_id, fornecedor_id, data, valor, descricao)
VALUES (?, ?, ?, ?, ?, ?)`;
db.run(query, [id, tipo_despesa_id, fornecedor_id || null, data, valor, descricao], function(err) {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({ id, message: 'Despesa cadastrada com sucesso!' });
});
});
app.put('/api/despesas/:id', (req, res) => {
const { id } = req.params;
const { tipo_despesa_id, fornecedor_id, data, valor, descricao } = req.body;
const query = `UPDATE despesas
SET tipo_despesa_id = ?, fornecedor_id = ?, data = ?, valor = ?, descricao = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?`;
db.run(query, [tipo_despesa_id, fornecedor_id || null, data, valor, descricao, id], function(err) {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({ message: 'Despesa atualizada com sucesso!' });
});
});
app.delete('/api/despesas/:id', (req, res) => {
const { id } = req.params;
db.run('DELETE FROM despesas WHERE id = ?', [id], function(err) {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({ message: 'Despesa excluída com sucesso!' });
});
});
// === VENDAS ===
app.get('/api/vendas', (req, res) => {
const query = `
SELECT v.*, c.nome_completo as cliente_nome
FROM vendas v
LEFT JOIN clientes c ON v.cliente_id = c.id
ORDER BY v.data_venda DESC
`;
db.all(query, [], (err, rows) => {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json(rows);
});
});
app.post('/api/vendas', (req, res) => {
const { cliente_id, tipo_pagamento, valor_total, desconto, parcelas, data_venda, observacoes, itens } = req.body;
const vendaId = uuidv4();
const valor_parcela = tipo_pagamento === 'parcelado' ? (valor_total - desconto) / parcelas : 0;
// Iniciar transação
db.serialize(() => {
db.run('BEGIN TRANSACTION');
// Inserir venda
const vendaQuery = `INSERT INTO vendas (id, cliente_id, tipo_pagamento, valor_total, desconto, parcelas, valor_parcela, data_venda, observacoes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`;
db.run(vendaQuery, [vendaId, cliente_id || null, tipo_pagamento, valor_total, desconto, parcelas, valor_parcela, data_venda, observacoes], function(err) {
if (err) {
db.run('ROLLBACK');
res.status(500).json({ error: err.message });
return;
}
// Inserir itens da venda
let itemsProcessed = 0;
const totalItems = itens.length;
if (totalItems === 0) {
db.run('COMMIT');
res.json({ id: vendaId, message: 'Venda registrada com sucesso!' });
return;
}
itens.forEach((item) => {
const itemId = uuidv4();
const itemQuery = `INSERT INTO venda_itens (id, venda_id, produto_variacao_id, quantidade, valor_unitario, valor_total)
VALUES (?, ?, ?, ?, ?, ?)`;
db.run(itemQuery, [itemId, vendaId, item.produto_variacao_id, item.quantidade, item.valor_unitario, item.valor_total], function(err) {
if (err) {
db.run('ROLLBACK');
res.status(500).json({ error: err.message });
return;
}
// Atualizar estoque
const updateEstoque = `UPDATE produto_variacoes
SET quantidade = quantidade - ?
WHERE id = ?`;
db.run(updateEstoque, [item.quantidade, item.produto_variacao_id], function(err) {
if (err) {
db.run('ROLLBACK');
res.status(500).json({ error: err.message });
return;
}
itemsProcessed++;
if (itemsProcessed === totalItems) {
db.run('COMMIT');
res.json({ id: vendaId, message: 'Venda registrada com sucesso!' });
}
});
});
});
});
});
});
app.delete('/api/vendas/:id', (req, res) => {
const { id } = req.params;
db.run('DELETE FROM vendas WHERE id = ?', [id], function(err) {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({ message: 'Venda excluída com sucesso!' });
});
});
// === TESTE ===
app.get('/api/test', (req, res) => {
res.json({ message: 'API funcionando corretamente!', timestamp: new Date() });
});
// === DASHBOARD ===
app.get('/api/dashboard', (req, res) => {
const queries = {
totalProdutos: 'SELECT COUNT(*) as count FROM produtos',
totalClientes: 'SELECT COUNT(*) as count FROM clientes',
totalFornecedores: 'SELECT COUNT(*) as count FROM fornecedores',
vendasMes: `SELECT COUNT(*) as count, SUM(valor_total) as total
FROM vendas
WHERE strftime('%Y-%m', data_venda) = strftime('%Y-%m', 'now')`,
estoqueTotal: 'SELECT SUM(quantidade) as total FROM produto_variacoes',
despesasMes: `SELECT SUM(valor) as total
FROM despesas
WHERE strftime('%Y-%m', data) = strftime('%Y-%m', 'now')`
};
const results = {};
let completed = 0;
const total = Object.keys(queries).length;
Object.entries(queries).forEach(([key, query]) => {
db.get(query, [], (err, row) => {
if (err) {
results[key] = { error: err.message };
} else {
results[key] = row;
}
completed++;
if (completed === total) {
res.json(results);
}
});
});
});
// Servir arquivos estáticos do React
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'client/build', 'index.html'));
});
// Criar diretório de uploads se não existir
const fs = require('fs');
if (!fs.existsSync('uploads')) {
fs.mkdirSync('uploads');
}
app.listen(PORT, () => {
console.log(`Servidor rodando na porta ${PORT}`);
});
module.exports = app;