Vertragsverwaltung_Plusfit24/app.js
2026-03-28 09:00:44 +00:00

176 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

require('dotenv').config();
const express = require('express');
const session = require('express-session');
const path = require('path');
const bcrypt = require('bcryptjs');
const db = require('./config/database');
const app = express();
// View Engine
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
// Static Files
app.use(express.static(path.join(__dirname, 'public')));
// Body Parser
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Session
app.use(session({
secret: process.env.SESSION_SECRET || 'plusfit24-secret',
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // auf true setzen wenn HTTPS direkt (nicht via Proxy)
maxAge: 24 * 60 * 60 * 1000 // 24 Stunden
}
}));
// Routen
const indexRouter = require('./routes/index');
const adminRouter = require('./routes/admin');
const apiRouter = require('./routes/api');
const billingRouter = require('./routes/billing');
const financeRouter = require('./routes/finance');
const renewalRouter = require('./routes/renewal');
const contractsRouter = require('./routes/contracts');
const mailingRouter = require('./routes/mailing');
const cron = require('node-cron');
app.use('/', indexRouter);
app.use('/admin', adminRouter);
app.use('/api', apiRouter);
app.use('/admin/billing', billingRouter);
app.use('/admin/finance', financeRouter);
app.use('/', renewalRouter);
app.use('/admin/contracts', contractsRouter);
app.use('/admin/mailing', mailingRouter);
// 404 Handler
app.use((req, res) => {
res.status(404).render('error', { message: 'Seite nicht gefunden' });
});
// Fehler Handler
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).render('error', { message: 'Ein Fehler ist aufgetreten' });
});
// Admin Account beim Start erstellen falls keiner existiert
async function initAdmin() {
try {
const [rows] = await db.query('SELECT COUNT(*) as count FROM admins');
if (rows[0].count === 0) {
const hash = await bcrypt.hash(process.env.ADMIN_PASSWORD || 'Admin1234!', 12);
await db.query(
'INSERT INTO admins (username, password_hash) VALUES (?, ?)',
[process.env.ADMIN_USER || 'admin', hash]
);
console.log('✅ Admin Account erstellt:', process.env.ADMIN_USER || 'admin');
}
} catch (err) {
console.error('❌ Fehler beim Erstellen des Admin Accounts:', err.message);
}
}
// Verlängerungs-E-Mails: täglich um 08:00 Uhr prüfen (60 Tage vorher)
cron.schedule('0 8 * * *', async () => {
console.log('⏰ Prüfe auslaufende Verträge (60 Tage)...');
try {
const [expiring] = await db.query(`
SELECT m.* FROM memberships m
WHERE m.status = 'active'
AND m.effective_end BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 61 DAY)
AND m.id NOT IN (
SELECT membership_id FROM renewal_requests
WHERE status IN ('pending','completed')
AND sent_at >= DATE_SUB(NOW(), INTERVAL 70 DAY)
)
`);
if (expiring.length > 0) {
const { renewalEmailHtml } = require('./routes/renewal');
const mailer = require('./config/mailer');
const crypto = require('crypto');
const [tariffs] = await db.query('SELECT * FROM tariffs WHERE active = 1 ORDER BY price_monthly ASC');
for (const member of expiring) {
try {
const token = crypto.randomBytes(32).toString('hex');
const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
await db.query(
'INSERT INTO renewal_requests (membership_id, token, expires_at) VALUES (?,?,?)',
[member.id, token, expiresAt]
);
const baseUrl = process.env.APP_URL || 'https://plusfit24.software-joksch.com';
const link = baseUrl + '/renew/' + token;
await mailer.sendMail({
from: process.env.MAIL_FROM,
to: member.email,
subject: `Deine PlusFit24 Mitgliedschaft läuft bald ab`,
html: renewalEmailHtml(member, tariffs, link, member.effective_end || member.contract_end)
});
await db.query(
'INSERT INTO email_log (membership_id, type, recipient, subject, status) VALUES (?,?,?,?,?)',
[member.id, 'renewal_auto', member.email, 'Mitgliedschaft läuft ab', 'sent']
);
console.log('📧 Verlängerungs-E-Mail gesendet an:', member.email);
} catch (err) {
console.error('❌ E-Mail Fehler für', member.email, ':', err.message);
}
}
} else {
console.log('✅ Keine auslaufenden Verträge heute.');
}
} catch (err) {
console.error('❌ Cron Fehler:', err.message);
}
});
// Auto-Abrechnungslauf jeden 1. des Monats um 06:00 Uhr
cron.schedule('0 6 1 * *', async () => {
const { currentPeriod } = require('./routes/billing');
const period = currentPeriod();
console.log(`⏰ Auto-Abrechnungslauf gestartet für ${period}`);
try {
const [existing] = await db.query('SELECT COUNT(*) as c FROM invoices WHERE period = ?', [period]);
if (existing[0].c > 0) {
console.log(`⏭ Abrechnungslauf für ${period} bereits vorhanden übersprungen`);
return;
}
const [members] = await db.query(`
SELECT m.*, t.price_monthly FROM memberships m
JOIN tariffs t ON m.tariff_id = t.id
WHERE m.status IN ('active','paused')
AND m.contract_start <= LAST_DAY(STR_TO_DATE(CONCAT(?, '-01'), '%Y-%m-%d'))
AND (m.contract_end IS NULL OR m.contract_end >= STR_TO_DATE(CONCAT(?, '-01'), '%Y-%m-%d'))
`, [period, period]);
const [runResult] = await db.query(
'INSERT INTO billing_runs (run_date, period, created_by) VALUES (CURDATE(), ?, ?)',
[period, 'system-auto']
);
let total = 0, count = 0;
for (const m of members) {
const firstPeriod = m.first_payment_date ? m.first_payment_date.toISOString().substring(0,7) : null;
const amount = firstPeriod === period && m.first_payment_amt ? parseFloat(m.first_payment_amt) : parseFloat(m.price_monthly);
await db.query(
'INSERT IGNORE INTO invoices (billing_run_id, membership_id, period, amount, description, iban, account_holder, bank_name) VALUES (?,?,?,?,?,?,?,?)',
[runResult.insertId, m.id, period, amount, `Mitgliedsbeitrag ${period}`, m.iban||'', m.account_holder||'', m.bank_name||'']
);
total += amount; count++;
}
await db.query('UPDATE billing_runs SET total_amount=?, invoice_count=? WHERE id=?', [total, count, runResult.insertId]);
console.log(`✅ Auto-Abrechnungslauf abgeschlossen: ${count} Rechnungen, ${total.toFixed(2)}`);
} catch (err) {
console.error('❌ Auto-Abrechnungslauf Fehler:', err.message);
}
});
const PORT = process.env.PORT || 3100;
app.listen(PORT, async () => {
console.log(`🚀 PlusFit24 Server läuft auf Port ${PORT}`);
await initAdmin();
});