Compare commits
2 Commits
616e518a8b
...
5160c1736f
| Author | SHA1 | Date |
|---|---|---|
|
|
5160c1736f | 7 months ago |
|
|
8735df5b43 | 7 months ago |
12 changed files with 909 additions and 79 deletions
@ -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 |
|
||||
@ -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 }); |
||||
|
} |
||||
|
}; |
||||
@ -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 |
||||
|
}; |
||||
@ -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 }); |
||||
|
} |
||||
|
}; |
||||
@ -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,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 }); |
||||
|
} |
||||
|
}; |
||||
@ -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); |
||||
|
} |
||||
|
}; |
||||
@ -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; |
||||
|
} |
||||
Loading…
Reference in new issue