Compare commits
2 Commits
616e518a8b
...
5160c1736f
| Author | SHA1 | Date | |
|---|---|---|---|
| 5160c1736f | |||
| 8735df5b43 |
@ -1,8 +0,0 @@
|
||||
PORT=3000
|
||||
JWT_SECRET=yourSuperSecretKey # Replace with your actual secret key
|
||||
|
||||
# Database configuration
|
||||
DB_HOST=localhost
|
||||
DB_USER=root
|
||||
DB_PASSWORD=
|
||||
DB_NAME=api_isakafo
|
||||
@ -27,69 +27,7 @@ async function initDB() {
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
|
||||
// table fournisseurs
|
||||
await connection.query(`
|
||||
CREATE TABLE IF NOT EXISTS fournisseurs (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
category VARCHAR(50) NOT NULL,
|
||||
contact_person VARCHAR(255) DEFAULT NULL,
|
||||
phone VARCHAR(20) DEFAULT NULL,
|
||||
email VARCHAR(255) DEFAULT NULL,
|
||||
status VARCHAR(20) DEFAULT 'active',
|
||||
last_order_date DATE DEFAULT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
|
||||
// Create emplacements table if it doesn't exist
|
||||
await connection.query(`
|
||||
CREATE TABLE IF NOT EXISTS emplacements (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
type VARCHAR(50) NOT NULL,
|
||||
temperature DECIMAL(5, 2) DEFAULT NULL,
|
||||
capacity INT DEFAULT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
|
||||
// table compartiments
|
||||
await connection.query(`
|
||||
CREATE TABLE IF NOT EXISTS compartiments (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
capacity INT DEFAULT NULL,
|
||||
uniter VARCHAR(50) NOT NULL DEFAULT 'kg',
|
||||
id_emplacement BIGINT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (id_emplacement) REFERENCES emplacements(id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
|
||||
// table stock
|
||||
await connection.query(`
|
||||
CREATE TABLE IF NOT EXISTS stocks (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY NOT NULL,
|
||||
articles VARCHAR(255) NOT NULL,
|
||||
quantity INT NOT NULL DEFAULT 0,
|
||||
uniter VARCHAR(50) NOT NULL DEFAULT 'kg',
|
||||
price_unit DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
||||
id_emplacement BIGINT NOT NULL,
|
||||
id_compartiment BIGINT NOT NULL,
|
||||
fournisseur_id BIGINT DEFAULT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (id_emplacement) REFERENCES emplacements(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (fournisseur_id) REFERENCES fournisseurs(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (id_compartiment) REFERENCES compartiments(id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
|
||||
|
||||
// ajoute une autre table si necessaire
|
||||
|
||||
// add a default admin user if none exists
|
||||
@ -99,8 +37,8 @@ async function initDB() {
|
||||
const hashedPassword = await bcrypt.hash('admin123', 10);
|
||||
|
||||
await connection.query(
|
||||
'INSERT INTO users (username, password, role) VALUES (?, ?, ?)',
|
||||
['admin', hashedPassword, 'admin']
|
||||
'INSERT INTO users (user_name,user_email, user_password, user_role) VALUES (?, ?, ?, ? )',
|
||||
['admin','valerien@gmai.com' ,hashedPassword, 'admin']
|
||||
);
|
||||
|
||||
console.log('✅ Default admin user created: admin / admin123');
|
||||
|
||||
144
controllers/CustomersController.js
Normal file
144
controllers/CustomersController.js
Normal file
@ -0,0 +1,144 @@
|
||||
const { pool } = require('../config/databases');
|
||||
const { validationResult } = require('express-validator');
|
||||
|
||||
// Lister les clients avec recherche et stats
|
||||
exports.index = async (req, res) => {
|
||||
const query = req.query.query || '';
|
||||
try {
|
||||
let [clients] = query
|
||||
? await pool.query(
|
||||
`SELECT * FROM clients WHERE name LIKE ? OR email LIKE ?`,
|
||||
[`%${query}%`, `%${query}%`]
|
||||
)
|
||||
: await db.query(`SELECT * FROM clients`);
|
||||
|
||||
const [totalClientsResult] = await pool.query(`SELECT COUNT(*) as total FROM clients`);
|
||||
const [loyalResult] = await pool.query(`SELECT COUNT(*) as loyal FROM clients WHERE membership IN ('Or', 'Argent')`);
|
||||
const [bronzeResult] = await pool.query(`SELECT COUNT(*) as bronze FROM clients WHERE membership = 'Bronze'`);
|
||||
const [argentResult] = await pool.query(`SELECT COUNT(*) as argent FROM clients WHERE membership = 'Argent'`);
|
||||
const [orResult] = await pool.query(`SELECT COUNT(*) as gold FROM clients WHERE membership = 'Or'`);
|
||||
|
||||
const avgSatisfaction = '4.6/5'; // À calculer depuis table avis
|
||||
const avgCart = '€45'; // À calculer depuis commandes
|
||||
|
||||
const fidelityLevels = {
|
||||
Bronze: {
|
||||
count: bronzeResult[0].bronze,
|
||||
benefits: ['5% de réduction', 'Offre anniversaire']
|
||||
},
|
||||
Argent: {
|
||||
count: argentResult[0].argent,
|
||||
benefits: ['10% de réduction', 'Réservation prioritaire', 'Menu dégustation offert']
|
||||
},
|
||||
Or: {
|
||||
count: orResult[0].gold,
|
||||
benefits: ['15% de réduction', 'Service VIP', 'Événements exclusifs', 'Livraison gratuite']
|
||||
}
|
||||
};
|
||||
|
||||
const marketingCampaigns = [
|
||||
{
|
||||
name: 'Newsletter hebdomadaire',
|
||||
description: 'Envoyée tous les vendredis',
|
||||
subscribers: 1247,
|
||||
type: 'newsletter'
|
||||
},
|
||||
{
|
||||
name: 'Offre spéciale Saint-Valentin',
|
||||
description: 'SMS ciblé',
|
||||
target: 342,
|
||||
type: 'sms'
|
||||
}
|
||||
];
|
||||
|
||||
res.json({
|
||||
searchQuery: query,
|
||||
clients,
|
||||
totalClients: totalClientsResult[0].total,
|
||||
loyalClients: loyalResult[0].loyal,
|
||||
avgSatisfaction,
|
||||
avgCart,
|
||||
fidelityLevels,
|
||||
marketingCampaigns
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Erreur serveur', details: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// Ajouter un nouveau client avec validation
|
||||
exports.save = async (req, res) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty())
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
|
||||
const { name, email, phone, visits, spent, rating, preferences } = req.body;
|
||||
|
||||
let membership = '';
|
||||
if (visits >= 20 && spent >= 1000000) membership = 'Or';
|
||||
else if (visits >= 10 && spent >= 500000) membership = 'Argent';
|
||||
else if (visits >= 10 && spent >= 300000) membership = 'Bronze';
|
||||
|
||||
try {
|
||||
await pool.query(
|
||||
`INSERT INTO clients (name, email, phone, visits, spent, rating, membership, preferences)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
name,
|
||||
email,
|
||||
phone || '',
|
||||
visits,
|
||||
spent,
|
||||
rating,
|
||||
membership,
|
||||
JSON.stringify(preferences || [])
|
||||
]
|
||||
);
|
||||
|
||||
res.status(201).json({ message: `Client "${name}" ajouté avec succès !` });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Erreur serveur', details: err.message });
|
||||
}
|
||||
};
|
||||
|
||||
// Afficher les détails d'un client
|
||||
exports.show = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
try {
|
||||
const [result] = await pool.query(`SELECT * FROM clients WHERE id = ?`, [id]);
|
||||
|
||||
if (result.length === 0)
|
||||
return res.status(404).json({ error: 'Client non trouvé' });
|
||||
|
||||
const client = result[0];
|
||||
|
||||
res.json({
|
||||
niveauAdhesion: client.membership,
|
||||
evaluation: client.rating
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Erreur serveur', details: err.message });
|
||||
}
|
||||
};
|
||||
|
||||
// Éditer un client (renvoyer les infos + moyenne des avis)
|
||||
exports.edit = async (req, res) => {
|
||||
const clientId = req.params.id;
|
||||
try {
|
||||
const [result] = await pool.query(`SELECT * FROM clients WHERE id = ?`, [clientId]);
|
||||
|
||||
if (result.length === 0)
|
||||
return res.status(404).json({ error: 'Client non trouvé' });
|
||||
|
||||
const client = result[0];
|
||||
const [noteResult] = await pool.query(`SELECT AVG(note) as note FROM avis WHERE client_id = ?`, [clientId]);
|
||||
const evaluation = noteResult[0].note || 0;
|
||||
|
||||
res.json({
|
||||
client,
|
||||
evaluation
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Erreur serveur', details: err.message });
|
||||
}
|
||||
};
|
||||
238
controllers/FinancesController.js
Normal file
238
controllers/FinancesController.js
Normal file
@ -0,0 +1,238 @@
|
||||
const { pool } = require('../config/databases');
|
||||
|
||||
// GET /finances
|
||||
const getDashboard = async (req, res) => {
|
||||
try {
|
||||
const [payments] = await pool.query(`
|
||||
SELECT id, description, payment_date AS date_transaction, 'Paiement' AS type_transaction, 'Recette' AS categorie, amount
|
||||
FROM payments
|
||||
ORDER BY payment_date DESC
|
||||
LIMIT 10
|
||||
`);
|
||||
|
||||
const [[{ totalRevenue }]] = await pool.query(`SELECT SUM(amount) AS totalRevenue FROM payments`);
|
||||
|
||||
let expenses = [], totalExpenses = 0, depenses_change = 0;
|
||||
|
||||
const [expensesTable] = await pool.query(`SHOW TABLES LIKE 'expenses'`);
|
||||
if (expensesTable.length > 0) {
|
||||
[expenses] = await db.query(`
|
||||
SELECT id, description, expense_date AS date_transaction, 'Dépense' AS type_transaction, 'Dépense' AS categorie, amount
|
||||
FROM expenses
|
||||
ORDER BY expense_date DESC
|
||||
LIMIT 10
|
||||
`);
|
||||
|
||||
const [[{ total }]] = await pool.query(`SELECT SUM(amount) AS total FROM expenses`);
|
||||
totalExpenses = total || 0;
|
||||
|
||||
const previousMonthExpenses = 800000;
|
||||
if (previousMonthExpenses > 0) {
|
||||
depenses_change = ((totalExpenses - previousMonthExpenses) / previousMonthExpenses) * 100;
|
||||
}
|
||||
}
|
||||
|
||||
const previousMonthRevenue = 2000000;
|
||||
const chiffre_affaires_change = (previousMonthRevenue > 0)
|
||||
? ((totalRevenue - previousMonthRevenue) / previousMonthRevenue) * 100
|
||||
: 0;
|
||||
|
||||
const benefice_net = totalRevenue - totalExpenses;
|
||||
const benefice_net_marge = totalRevenue > 0 ? (benefice_net / totalRevenue) * 100 : 0;
|
||||
const tresorerie = benefice_net;
|
||||
|
||||
const dashboard_stats = {
|
||||
chiffre_affaires: totalRevenue,
|
||||
chiffre_affaires_change,
|
||||
depenses: totalExpenses,
|
||||
depenses_change,
|
||||
benefices: benefice_net,
|
||||
benefice_net,
|
||||
benefice_net_marge,
|
||||
tresorerie,
|
||||
tresorerie_solde: tresorerie,
|
||||
total_liabilities: 0
|
||||
};
|
||||
|
||||
const profitLoss = {
|
||||
revenu_total: totalRevenue,
|
||||
depense_total: totalExpenses,
|
||||
benefice_net,
|
||||
marge: benefice_net_marge
|
||||
};
|
||||
|
||||
const balanceSheet = {
|
||||
total_assets: 0,
|
||||
total_liabilities: 0,
|
||||
total_equity: 0,
|
||||
net_worth: 0
|
||||
};
|
||||
|
||||
const transactions = [...payments, ...expenses].sort((a, b) =>
|
||||
new Date(b.date_transaction) - new Date(a.date_transaction)
|
||||
);
|
||||
|
||||
const [[invoiceTable]] = await pool.query(`SHOW TABLES LIKE 'invoices'`);
|
||||
let invoices = [];
|
||||
if (invoiceTable) {
|
||||
[invoices] = await pool.query(`
|
||||
SELECT id, invoice_number, client_name, invoice_date, status, total_amount
|
||||
FROM invoices
|
||||
ORDER BY invoice_date DESC
|
||||
LIMIT 10
|
||||
`);
|
||||
}
|
||||
|
||||
res.json({
|
||||
transactions,
|
||||
dashboard_stats,
|
||||
reportDate: new Date().toISOString().slice(0, 10),
|
||||
profitLoss,
|
||||
balanceSheet,
|
||||
invoices
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Erreur dans getDashboard:", err.message);
|
||||
res.status(500).json({ error: "Erreur serveur" });
|
||||
}
|
||||
};
|
||||
|
||||
// POST /finances/invoices
|
||||
const generateInvoice = async (req, res) => {
|
||||
const {
|
||||
invoice_number,
|
||||
client_name,
|
||||
client_email,
|
||||
client_address,
|
||||
invoice_date,
|
||||
due_date,
|
||||
total_amount,
|
||||
status = 'Draft',
|
||||
notes
|
||||
} = req.body;
|
||||
|
||||
try {
|
||||
await pool.query(`
|
||||
INSERT INTO invoices (invoice_number, client_name, client_email, client_address, invoice_date, due_date, total_amount, status, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, [
|
||||
invoice_number.trim(),
|
||||
client_name.trim(),
|
||||
client_email?.trim() || null,
|
||||
client_address.trim(),
|
||||
invoice_date || new Date(),
|
||||
due_date || null,
|
||||
parseFloat(total_amount),
|
||||
status.trim(),
|
||||
notes?.trim()
|
||||
]);
|
||||
|
||||
res.status(201).json({ message: 'Facture ajoutée avec succès.' });
|
||||
} catch (err) {
|
||||
console.error("Erreur dans generateInvoice:", err.message);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
};
|
||||
|
||||
// POST /finances/payments
|
||||
const processPayment = async (req, res) => {
|
||||
const { invoice_id, payment_date, amount, payment_method, description } = req.body;
|
||||
const conn = await pool.getConnection();
|
||||
|
||||
try {
|
||||
await conn.beginTransaction();
|
||||
|
||||
await conn.query(`
|
||||
INSERT INTO payments (invoice_id, payment_date, amount, payment_method, description)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`, [
|
||||
invoice_id,
|
||||
payment_date || new Date(),
|
||||
parseFloat(amount),
|
||||
payment_method.trim(),
|
||||
description.trim()
|
||||
]);
|
||||
|
||||
const [[{ total_amount }]] = await conn.query(`SELECT total_amount FROM invoices WHERE id = ?`, [invoice_id]);
|
||||
const [[{ total_paid }]] = await conn.query(`SELECT SUM(amount) AS total_paid FROM payments WHERE invoice_id = ?`, [invoice_id]);
|
||||
|
||||
let status = 'Partially Paid';
|
||||
if (Math.abs(total_amount - total_paid) < 0.01) {
|
||||
status = 'Paid';
|
||||
}
|
||||
|
||||
await conn.query(`UPDATE invoices SET status = ? WHERE id = ?`, [status, invoice_id]);
|
||||
|
||||
await conn.query(`
|
||||
UPDATE balance_sheet_accounts
|
||||
SET amount = amount + ?
|
||||
WHERE name = 'Trésorerie' AND account_type = 'Asset'
|
||||
`, [amount]);
|
||||
|
||||
await conn.commit();
|
||||
res.status(200).json({ message: 'Paiement enregistré avec succès.' });
|
||||
|
||||
} catch (err) {
|
||||
await conn.rollback();
|
||||
console.error("Erreur dans processPayment:", err.message);
|
||||
res.status(500).json({ error: err.message });
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
};
|
||||
|
||||
// GET + POST /finances/taxes
|
||||
const getTaxes = async (req, res) => {
|
||||
const [taxes] = await pool.query(`SELECT * FROM taxes ORDER BY name ASC`);
|
||||
res.json(taxes);
|
||||
};
|
||||
|
||||
const createOrUpdateTax = async (req, res) => {
|
||||
const { id, name, rate, description, is_active } = req.body;
|
||||
|
||||
if (!name || isNaN(rate)) {
|
||||
return res.status(400).json({ error: 'Nom et taux valides requis.' });
|
||||
}
|
||||
|
||||
try {
|
||||
if (id) {
|
||||
await pool.query(`
|
||||
UPDATE taxes SET name = ?, rate = ?, description = ?, is_active = ?
|
||||
WHERE id = ?
|
||||
`, [name.trim(), rate, description?.trim(), is_active ? 1 : 0, id]);
|
||||
} else {
|
||||
await pool.query(`
|
||||
INSERT INTO taxes (name, rate, description, is_active)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`, [name.trim(), rate, description?.trim(), is_active ? 1 : 0]);
|
||||
}
|
||||
|
||||
res.json({ message: 'Taxe enregistrée.' });
|
||||
} catch (err) {
|
||||
console.error("Erreur dans createOrUpdateTax:", err.message);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
};
|
||||
|
||||
// DELETE /finances/taxes/:id
|
||||
const disableTax = async (req, res) => {
|
||||
const id = parseInt(req.params.id);
|
||||
if (!id) return res.status(400).json({ error: "ID invalide" });
|
||||
|
||||
try {
|
||||
await pool.query(`UPDATE taxes SET is_active = 0 WHERE id = ?`, [id]);
|
||||
res.json({ message: 'Taxe désactivée.' });
|
||||
} catch (err) {
|
||||
console.error("Erreur dans disableTax:", err.message);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getDashboard,
|
||||
generateInvoice,
|
||||
processPayment,
|
||||
getTaxes,
|
||||
createOrUpdateTax,
|
||||
disableTax
|
||||
};
|
||||
59
controllers/ModulesController.js
Normal file
59
controllers/ModulesController.js
Normal file
@ -0,0 +1,59 @@
|
||||
const { pool } = require('../config/databases');
|
||||
|
||||
// Afficher la liste des modules avec leur état pour un établissement donné
|
||||
exports.index = async (req, res) => {
|
||||
try {
|
||||
const etablissementId = 1; // À remplacer par auth ou token utilisateur
|
||||
|
||||
const [modules] = await pool.query(`SELECT * FROM modules`);
|
||||
const [actifsRaw] = await pool.query(`
|
||||
SELECT module_id, est_actif
|
||||
FROM etablissement_modules
|
||||
WHERE etablissement_id = ?`,
|
||||
[etablissementId]
|
||||
);
|
||||
|
||||
const actifs = {};
|
||||
actifsRaw.forEach(row => {
|
||||
actifs[row.module_id] = !!row.est_actif;
|
||||
});
|
||||
|
||||
res.render('modules/index', { modules, actifs });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Erreur serveur', details: err.message });
|
||||
}
|
||||
};
|
||||
|
||||
// Activer/désactiver un module (AJAX)
|
||||
exports.toggle = async (req, res) => {
|
||||
const { id, actif } = req.body;
|
||||
const moduleId = parseInt(id, 10);
|
||||
const etablissementId = 1; // Authentification future ici
|
||||
|
||||
try {
|
||||
const [existing] = await pool.query(
|
||||
`SELECT id FROM etablissement_modules
|
||||
WHERE module_id = ? AND etablissement_id = ?`,
|
||||
[moduleId, etablissementId]
|
||||
);
|
||||
|
||||
if (existing.length > 0) {
|
||||
await pool.query(
|
||||
`UPDATE etablissement_modules
|
||||
SET est_actif = ?
|
||||
WHERE id = ?`,
|
||||
[actif ? 1 : 0, existing[0].id]
|
||||
);
|
||||
} else {
|
||||
await pool.query(
|
||||
`INSERT INTO etablissement_modules (module_id, etablissement_id, est_actif)
|
||||
VALUES (?, ?, ?)`,
|
||||
[moduleId, etablissementId, actif ? 1 : 0]
|
||||
);
|
||||
}
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
};
|
||||
147
controllers/ReservtionController.js
Normal file
147
controllers/ReservtionController.js
Normal file
@ -0,0 +1,147 @@
|
||||
const reservationModel = require('../models/reservationModel');
|
||||
const tableModel = require('../models/tableModel');
|
||||
const invoiceModel = require('../models/invoiceModel');
|
||||
|
||||
// Helper pour filtrer selon la période
|
||||
const filterByPeriod = (reservations, period) => {
|
||||
const today = new Date();
|
||||
const todayStr = today.toISOString().slice(0, 10);
|
||||
|
||||
return reservations.filter((res) => {
|
||||
const resDate = new Date(res.date_reservation).toISOString().slice(0, 10);
|
||||
|
||||
switch (period) {
|
||||
case 'aujourdhui':
|
||||
return resDate === todayStr;
|
||||
case 'demain':
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(today.getDate() + 1);
|
||||
return resDate === tomorrow.toISOString().slice(0, 10);
|
||||
case 'cette-semaine':
|
||||
const day = today.getDay();
|
||||
const diffToMonday = today.getDate() - day + (day === 0 ? -6 : 1);
|
||||
const monday = new Date(today.setDate(diffToMonday));
|
||||
const sunday = new Date(monday);
|
||||
sunday.setDate(monday.getDate() + 6);
|
||||
|
||||
return new Date(res.date_reservation) >= monday && new Date(res.date_reservation) <= sunday;
|
||||
case 'toutes':
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// GET /reservations
|
||||
const index = async (req, res) => {
|
||||
const period = req.query.period || 'aujourdhui';
|
||||
const allReservations = await reservationModel.getAll();
|
||||
const tables = await tableModel.getAll();
|
||||
|
||||
const filteredReservations = filterByPeriod(allReservations, period);
|
||||
|
||||
const confirmedReservations = filteredReservations.filter(r => r.statut_reservation === 'Confirmée').length;
|
||||
const pendingReservations = filteredReservations.filter(r => r.statut_reservation === 'En attente').length;
|
||||
const estimatedRevenue = filteredReservations.reduce((acc, r) => acc + (parseFloat(r.montant_estime_mga) || 0), 0);
|
||||
|
||||
const reservedTables = [...new Set(
|
||||
filteredReservations
|
||||
.filter(r => r.type_reservation === 'table' && !['Annulée', 'Terminée'].includes(r.statut_reservation))
|
||||
.map(r => r.numero_table_chambre)
|
||||
)].length;
|
||||
|
||||
const occupancyRate = tables.length > 0 ? Math.round((reservedTables / tables.length) * 100) : 0;
|
||||
|
||||
res.json({
|
||||
currentPeriod: period,
|
||||
filteredReservations,
|
||||
tables,
|
||||
confirmedReservations,
|
||||
pendingReservations,
|
||||
estimatedRevenue,
|
||||
occupancyRate
|
||||
});
|
||||
};
|
||||
|
||||
// POST /reservations
|
||||
const store = async (req, res) => {
|
||||
try {
|
||||
const data = req.body;
|
||||
|
||||
const reservationId = await reservationModel.insert(data);
|
||||
|
||||
if (reservationId) {
|
||||
const lastInvoice = await invoiceModel.getLast();
|
||||
let newInvoiceNumber = 'F00001';
|
||||
|
||||
if (lastInvoice && lastInvoice.invoice_number.match(/F(\d+)/)) {
|
||||
const num = parseInt(RegExp.$1);
|
||||
newInvoiceNumber = 'F' + String(num + 1).padStart(5, '0');
|
||||
}
|
||||
|
||||
await invoiceModel.insert({
|
||||
invoice_number: newInvoiceNumber,
|
||||
client_name: data.client_nom,
|
||||
client_email: data.email || null,
|
||||
invoice_date: new Date(),
|
||||
due_date: null,
|
||||
total_amount: parseFloat(data.montant_estime_mga) || 0,
|
||||
status: 'Draft',
|
||||
reservation_id: reservationId,
|
||||
notes: data.notes_speciales || null
|
||||
});
|
||||
}
|
||||
|
||||
res.status(201).json({ message: 'Réservation et facture ajoutées avec succès.' });
|
||||
} catch (err) {
|
||||
console.error('Erreur dans store():', err.message);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
};
|
||||
|
||||
// DELETE /reservations/:id
|
||||
const destroy = async (req, res) => {
|
||||
const { id } = req.params;
|
||||
await reservationModel.delete(id);
|
||||
res.json({ message: 'Réservation supprimée.' });
|
||||
};
|
||||
|
||||
// GET /reservations/:id/edit
|
||||
const edit = async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const reservation = await reservationModel.findById(id);
|
||||
if (!reservation) {
|
||||
return res.status(404).json({ error: 'Réservation introuvable.' });
|
||||
}
|
||||
res.json({ reservation });
|
||||
};
|
||||
|
||||
// PUT /reservations/:id
|
||||
const update = async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const data = req.body;
|
||||
|
||||
if (!data.client_nom || data.client_nom.length < 3) {
|
||||
return res.status(400).json({ error: 'Nom client invalide.' });
|
||||
}
|
||||
|
||||
// Autres validations manuelles ici si besoin
|
||||
|
||||
await reservationModel.update(id, data);
|
||||
res.json({ message: 'Réservation mise à jour.' });
|
||||
};
|
||||
|
||||
// GET /reservations/create?table=3
|
||||
const create = (req, res) => {
|
||||
const tableId = req.query.table;
|
||||
res.json({ table: tableId });
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
index,
|
||||
store,
|
||||
destroy,
|
||||
edit,
|
||||
update,
|
||||
create
|
||||
};
|
||||
0
controllers/SettingsController.js
Normal file
0
controllers/SettingsController.js
Normal file
106
controllers/TablesController.js
Normal file
106
controllers/TablesController.js
Normal file
@ -0,0 +1,106 @@
|
||||
const { pool } = require('../config/databases');
|
||||
|
||||
exports.index = async (req, res) => {
|
||||
try {
|
||||
const [tables] = await pool.query('SELECT * FROM tables');
|
||||
res.render('tables/index', { tables });
|
||||
} catch (err) {
|
||||
res.status(500).send('Erreur serveur : ' + err.message);
|
||||
}
|
||||
};
|
||||
|
||||
// Affiche formulaire création (rendu simple, adapter selon moteur de vue)
|
||||
exports.create = (req, res) => {
|
||||
res.render('tables/create');
|
||||
};
|
||||
|
||||
// Enregistre nouvelle table
|
||||
exports.store = async (req, res) => {
|
||||
const { numero_table, capacite, statut_actuel } = req.body;
|
||||
|
||||
try {
|
||||
await pool.query(
|
||||
`INSERT INTO tables (numero_table, capacite, statut_actuel, id_reservation_actuelle)
|
||||
VALUES (?, ?, ?, NULL)`,
|
||||
[numero_table, capacite, statut_actuel]
|
||||
);
|
||||
res.redirect('/tables?success=Table ajoutée avec succès.');
|
||||
} catch (err) {
|
||||
// En cas d'erreur, tu peux passer l'erreur en query string ou gérer autrement
|
||||
res.redirect('/tables/create?error=' + encodeURIComponent(err.message));
|
||||
}
|
||||
};
|
||||
|
||||
// Affiche formulaire édition
|
||||
exports.edit = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT * FROM tables WHERE id = ?', [id]);
|
||||
if (rows.length === 0) {
|
||||
return res.status(404).send('Table non trouvée');
|
||||
}
|
||||
res.render('tables/edit', { table: rows[0] });
|
||||
} catch (err) {
|
||||
res.status(500).send('Erreur serveur : ' + err.message);
|
||||
}
|
||||
};
|
||||
|
||||
// Met à jour une table
|
||||
exports.update = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
const data = req.body;
|
||||
|
||||
try {
|
||||
// Mettre à jour la table
|
||||
const [result] = await pool.query(
|
||||
`UPDATE tables SET numero_table = ?, capacite = ?, statut_actuel = ? WHERE id = ?`,
|
||||
[data.numero_table, data.capacite, data.statut_actuel, id]
|
||||
);
|
||||
res.redirect('/tables?success=Table modifiée avec succès.');
|
||||
} catch (err) {
|
||||
res.redirect(`/tables/${id}/edit?error=` + encodeURIComponent(err.message));
|
||||
}
|
||||
};
|
||||
|
||||
// Supprimer une table
|
||||
exports.delete = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
try {
|
||||
await pool.query('DELETE FROM tables WHERE id = ?', [id]);
|
||||
res.redirect('/tables?success=Table supprimée.');
|
||||
} catch (err) {
|
||||
res.status(500).send('Erreur serveur : ' + err.message);
|
||||
}
|
||||
};
|
||||
|
||||
// Afficher détails JSON d'une table + client réservation en cours
|
||||
exports.show = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
try {
|
||||
const [tables] = await pool.query('SELECT * FROM tables WHERE id = ?', [id]);
|
||||
if (tables.length === 0) {
|
||||
return res.status(404).json({ error: 'Table non trouvée' });
|
||||
}
|
||||
const table = tables[0];
|
||||
|
||||
|
||||
const [reservations] = await pool.query(`
|
||||
SELECT client_nom FROM reservations
|
||||
WHERE type_reservation = 'table'
|
||||
AND numero_table_chambre = ?
|
||||
AND statut_reservation IN ('Confirmée', 'En attente')
|
||||
LIMIT 1
|
||||
`, [table.numero_table]);
|
||||
|
||||
const reservation = reservations.length > 0 ? reservations[0] : {};
|
||||
|
||||
res.json({
|
||||
capacite: table.capacite,
|
||||
details: table.details || 'N/A',
|
||||
statut_actuel: table.statut_actuel,
|
||||
client_nom: reservation.client_nom || null,
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Erreur serveur', details: err.message });
|
||||
}
|
||||
};
|
||||
12
controllers/TransactionsController.js
Normal file
12
controllers/TransactionsController.js
Normal file
@ -0,0 +1,12 @@
|
||||
const { pool } = require('../config/databases');
|
||||
|
||||
exports.index = async (req, res) => {
|
||||
try {
|
||||
const [transactions] = await pool.query(
|
||||
'SELECT * FROM transactions ORDER BY date_transaction DESC'
|
||||
);
|
||||
res.render('transactions/transactions_content', { transactions });
|
||||
} catch (err) {
|
||||
res.status(500).send('Erreur serveur : ' + err.message);
|
||||
}
|
||||
};
|
||||
126
controllers/staffsController.js
Normal file
126
controllers/staffsController.js
Normal file
@ -0,0 +1,126 @@
|
||||
const { pool } = require('../config/databases');
|
||||
|
||||
// 📌 Afficher la liste des employés
|
||||
exports.index = async (req, res) => {
|
||||
try {
|
||||
const [staffs] = await pool.query('SELECT * FROM staffs');
|
||||
res.json(staffs);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Erreur lors de la récupération des employés' });
|
||||
}
|
||||
};
|
||||
|
||||
// 📌 Afficher un employé pour modification
|
||||
exports.edit = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT * FROM staffs WHERE id = ?', [id]);
|
||||
if (rows.length === 0) {
|
||||
return res.status(404).json({ message: 'Employé non trouvé' });
|
||||
}
|
||||
res.json(rows[0]);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Erreur lors de la récupération de l\'employé' });
|
||||
}
|
||||
};
|
||||
|
||||
// 🖊️ Mettre à jour un employé
|
||||
exports.update = async (req, res) => {
|
||||
const id = req.params.id;
|
||||
const data = req.body;
|
||||
|
||||
if (data.planning && typeof data.planning === 'object') {
|
||||
data.planning = JSON.stringify(data.planning);
|
||||
data.heures_semaine = calculerHeuresSemaine(req.body.planning);
|
||||
}
|
||||
|
||||
try {
|
||||
await pool.query('UPDATE staffs SET ? WHERE id = ?', [data, id]);
|
||||
res.json({ message: 'Employé mis à jour avec succès' });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Erreur lors de la mise à jour' });
|
||||
}
|
||||
};
|
||||
|
||||
// ➕ Afficher formulaire de création (en REST on ne fait rien ici)
|
||||
exports.create = (req, res) => {
|
||||
res.json({ message: 'Créer un nouvel employé' });
|
||||
};
|
||||
|
||||
// 🗂️ Enregistrer un nouvel employé
|
||||
exports.store = async (req, res) => {
|
||||
const data = req.body;
|
||||
|
||||
// Gérer le champ "nouveau poste"
|
||||
if (data.poste === 'nouveau_poste') {
|
||||
if (!data.nouveau_poste || data.nouveau_poste.trim() === '') {
|
||||
return res.status(400).json({ error: 'Le champ Nouveau poste est requis.' });
|
||||
}
|
||||
data.poste = data.nouveau_poste.trim();
|
||||
}
|
||||
|
||||
// Convertir planning + calcul heures
|
||||
if (data.planning && typeof data.planning === 'object') {
|
||||
data.planning = JSON.stringify(data.planning);
|
||||
data.heures_semaine = calculerHeuresSemaine(req.body.planning);
|
||||
} else {
|
||||
data.heures_semaine = 0;
|
||||
}
|
||||
|
||||
data.performance = 0;
|
||||
|
||||
// Validation simple
|
||||
if (!data.nom || !data.prenom || !data.poste || !data.telephone) {
|
||||
return res.status(400).json({ error: 'Champs obligatoires manquants.' });
|
||||
}
|
||||
|
||||
try {
|
||||
await pool.query('INSERT INTO staffs SET ?', [data]);
|
||||
res.status(201).json({ message: '✅ Employé ajouté avec succès' });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Erreur lors de l\'ajout de l\'employé' });
|
||||
}
|
||||
};
|
||||
|
||||
// 📬 Simuler le contact de l’employé
|
||||
exports.contact = (req, res) => {
|
||||
const id = req.params.id;
|
||||
// Logique simulée
|
||||
res.json({ message: `Contact simulé avec l'employé ID ${id}` });
|
||||
};
|
||||
|
||||
// 🧠 Calculer le statut de l’employé (présent/absent)
|
||||
exports.calculerStatutEmploye = async (req, res) => {
|
||||
const { employeId, date } = req.params;
|
||||
try {
|
||||
const [pointage] = await pool.query(
|
||||
`SELECT * FROM pointages WHERE employe_id = ? AND date = ?`,
|
||||
[employeId, date]
|
||||
);
|
||||
|
||||
if (pointage.length === 0) return res.json({ statut: 'absent' });
|
||||
|
||||
const employePresent = pointage[0].heure_sortie === null ? 'present' : 'present';
|
||||
res.json({ statut: employePresent });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Erreur lors de la vérification du statut' });
|
||||
}
|
||||
};
|
||||
|
||||
// 🕒 Fonction utilitaire pour calculer les heures à partir du planning
|
||||
function calculerHeuresSemaine(planning) {
|
||||
let totalHeures = 0;
|
||||
|
||||
for (const jour in planning) {
|
||||
const horaires = planning[jour];
|
||||
if (horaires.de && horaires.a) {
|
||||
const debut = new Date(`1970-01-01T${horaires.de}:00`);
|
||||
const fin = new Date(`1970-01-01T${horaires.a}:00`);
|
||||
const diffMs = fin - debut;
|
||||
const heures = diffMs / (1000 * 60 * 60);
|
||||
if (heures > 0) totalHeures += heures;
|
||||
}
|
||||
}
|
||||
|
||||
return totalHeures;
|
||||
}
|
||||
@ -11,7 +11,7 @@ router.post('/login', async (req, res) => {
|
||||
|
||||
try {
|
||||
const [rows] = await pool.query(
|
||||
'SELECT * FROM users WHERE username = ?',
|
||||
'SELECT * FROM users WHERE user_name = ?',
|
||||
[username]
|
||||
);
|
||||
|
||||
@ -21,7 +21,7 @@ router.post('/login', async (req, res) => {
|
||||
|
||||
const user = rows[0];
|
||||
|
||||
const isMatch = await bcrypt.compare(password, user.password);
|
||||
const isMatch = await bcrypt.compare(password, user.user_password);
|
||||
|
||||
if (!isMatch) {
|
||||
return res.status(401).json({ message: 'username or password incorect' });
|
||||
|
||||
@ -1,11 +1,18 @@
|
||||
const express = require('express');
|
||||
const authMiddleware = require('../middleware/authMiddleware');
|
||||
const userController = require('../controllers/UserController');
|
||||
const emplacementController = require('../controllers/EmplacementController');
|
||||
const compartimentController = require('../controllers/CompartimentController');
|
||||
const stockController = require('../controllers/StockController');
|
||||
const staffController = require('../controllers/staffsController');
|
||||
const emplacementController = require('../controllers/emplacementController');
|
||||
const compartimentController = require('../controllers/compartimentController');
|
||||
const stockController = require('../controllers/stockController');
|
||||
const fournisseurController = require('../controllers/FournisseurController');
|
||||
|
||||
const reservationController = require('../controllers/ReservationController');
|
||||
const financesController = require('../controllers/FinancesController');
|
||||
const CustomersController = require('../controllers/CustomersController');
|
||||
const SettingsController = require('../controllers/SettingsController');
|
||||
const ModulesController = require('../controllers/ModulesController');
|
||||
const TablesController = require('../controllers/TablesController');
|
||||
const TransactionsController = require('../controllers/TransactionsController');
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/profile', authMiddleware(), userController.getProfile);
|
||||
@ -22,5 +29,66 @@ router.get('/ingredients/emplacement', authMiddleware(), stockController.getIngr
|
||||
router.post('/create/fournisseur', authMiddleware(), fournisseurController.createFournisseur);
|
||||
router.get('/fournisseurs', authMiddleware(), fournisseurController.getFournisseurs);
|
||||
|
||||
router.get('/staffs', authMiddleware(), staffController.index);
|
||||
router.get('/staffs/create', authMiddleware('admin'), staffController.create);
|
||||
router.post('/staffs/store', authMiddleware('admin'), staffController.store);
|
||||
router.get('/staffs/:id/edit', authMiddleware(), staffController.edit);
|
||||
router.post('/staffs/:id/update', authMiddleware('admin'), staffController.update);
|
||||
router.get('/staffs/:id/contact', authMiddleware(), staffController.contact);
|
||||
router.get('/staffs/:employeId/statut/:date', authMiddleware(), staffController.calculerStatutEmploye);
|
||||
|
||||
router.get('/reservations', authMiddleware(), reservationController.index); // équivalent de $routes->get('/reservations', ...)
|
||||
router.get('/reservations/ajax', authMiddleware(), reservationController.getReservationsAjax); // si cette méthode existe
|
||||
router.get('/reservations/create', authMiddleware(), reservationController.create);
|
||||
router.post('/reservations/store', authMiddleware(), reservationController.store);
|
||||
router.get('/reservations/:id/edit', authMiddleware(), reservationController.edit);
|
||||
router.put('/reservations/:id', authMiddleware(), reservationController.update);
|
||||
router.delete('/reservations/:id', authMiddleware(), reservationController.destroy);
|
||||
|
||||
router.get('/finances', authMiddleware(), financesController.getDashboard);
|
||||
router.post('/finances/invoices', authMiddleware(), financesController.generateInvoice);
|
||||
router.post('/finances/payments', authMiddleware(), financesController.processPayment);
|
||||
router.get('/finances/taxes', authMiddleware(), financesController.getTaxes);
|
||||
router.post('/finances/taxes', authMiddleware(), financesController.createOrUpdateTax);
|
||||
router.delete('/finances/taxes/:id', authMiddleware(), financesController.disableTax);
|
||||
|
||||
router.get('/customers',authMiddleware(), CustomersController.index); // Liste des clients
|
||||
router.get('/customers/new', (req, res) => res.send('Formulaire nouveau client')); // Optionnel
|
||||
router.post('/customers/save', authMiddleware(), CustomersController.save); // Enregistrer un client
|
||||
router.get('/customers/:id',authMiddleware(), CustomersController.show);
|
||||
router.get('customers/:id/edit', authMiddleware(), CustomersController.edit);
|
||||
router.put('customers/:id/uptade',authMiddleware(), CustomersController.update);
|
||||
router.delete('customers/:id/delete',authMiddleware(), CustomersController.delete);
|
||||
|
||||
|
||||
router.get('/settings', authMiddleware(), SettingsController.getSettingsPage);
|
||||
router.post('/settings/restaurant', authMiddleware(), SettingsController.updateRestaurantInfo);
|
||||
router.post('/settings/opening-hours', authMiddleware(), SettingsController.updateOpeningHours);
|
||||
router.post('/settings/notifications', authMiddleware(), SettingsController.updateNotifications);
|
||||
router.get('/settings/users/new', authMiddleware(), SettingsController.addUser);
|
||||
router.post('/settings/users', authMiddleware(), SettingsController.saveUser);
|
||||
router.get('/settings/users/:id/edit', authMiddleware(), SettingsController.editUser);
|
||||
router.put('/settings/users/:id', authMiddleware(), SettingsController.updateUser);
|
||||
router.delete('/settings/users/:id', authMiddleware(), SettingsController.deleteUser);
|
||||
router.post('/settings/change-password', authMiddleware(), SettingsController.changePassword);
|
||||
router.post('/settings/two-factor', authMiddleware(), SettingsController.toggleTwoFactorAuth);
|
||||
router.get('/settings/integrations/booking', authMiddleware(), SettingsController.connectBooking);
|
||||
router.get('/settings/integrations/quickbooks', authMiddleware(), SettingsController.connectQuickbooks);
|
||||
router.get('/settings/integrations/stripe', authMiddleware(), SettingsController.connectStripe);
|
||||
router.get('/settings/modules', authMiddleware(), SettingsController.getModules);
|
||||
|
||||
router.get('/modules', authMiddleware(), ModulesController.index);
|
||||
router.post('/modules/toggle', authMiddleware(), ModulesController.toggle);
|
||||
|
||||
router.get('/tables', authMiddleware(), TablesController.index);
|
||||
router.get('/tables/create', authMiddleware(), TablesController.create);
|
||||
router.post('/tables', authMiddleware(), TablesController.store);
|
||||
router.get('/tables/:id/edit', authMiddleware(), TablesController.edit);
|
||||
router.post('/tables/:id/update', authMiddleware(), TablesController.update);
|
||||
router.post('/tables/:id/delete', authMiddleware(), TablesController.delete);
|
||||
router.get('/tables/:id', authMiddleware(), TablesController.show);
|
||||
|
||||
|
||||
router.get('/transactions', authMiddleware(), TransactionsController.index);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user