feat: system settings, dynamic logos, template numbering, permission consolidation

- System settings page with tabs: Security, System, Firma
- Configurable attendance rules (break thresholds, rounding) from DB
- Configurable document numbering with template patterns ({YYYY}/{PREFIX}/{NNN})
- Dynamic logo upload (light/dark variants) served from DB instead of static files
- Email settings (SMTP from/name, alert/leave emails) configurable in UI
- Currency and VAT rate lists configurable, used across all modules
- Permissions simplified: offers.settings + settings.roles + settings.security → settings.manage
- Leaflet bundled locally, removed unpkg.com from CSP
- Silent catch blocks fixed with proper logging
- console.log replaced with app.log.info in server.ts
- Schema renamed: company-settings.schema → settings.schema
- App info section: version, Node.js, uptime, memory, DB status, NAS status

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-03-27 10:15:47 +01:00
parent f49015a627
commit 6b31b2f74b
43 changed files with 2094 additions and 525 deletions

View File

@@ -5,6 +5,7 @@ import prisma from "../../config/database";
import { requireAuth, requirePermission } from "../../middleware/auth";
import { success, error } from "../../utils/response";
import { encrypt } from "../../utils/encryption";
import { getSystemSettings } from "../../services/system-settings";
import { OTPAuth } from "../../utils/totp";
import * as OTPAuthLib from "otpauth";
import { logAudit } from "../../services/audit";
@@ -16,9 +17,18 @@ export default async function totpRoutes(
): Promise<void> {
// GET - generate new TOTP secret
fastify.get("/setup", { preHandler: requireAuth }, async (request, reply) => {
const settings = await getSystemSettings();
const companyName =
(
await prisma.company_settings.findFirst({
select: { company_name: true },
})
)?.company_name ||
settings.smtp_from_name ||
"System";
const secret = new OTPAuthLib.Secret();
const totp = new OTPAuthLib.TOTP({
issuer: "BOHA Automation",
issuer: companyName,
label: request.authData!.email,
secret,
algorithm: "SHA1",
@@ -153,7 +163,7 @@ export default async function totpRoutes(
// GET - check if 2FA is required company-wide
fastify.get(
"/required",
{ preHandler: [requireAuth, requirePermission("settings.security")] },
{ preHandler: [requireAuth, requirePermission("settings.manage")] },
async (request, reply) => {
const settings = await prisma.company_settings.findFirst({
select: { require_2fa: true },
@@ -167,7 +177,7 @@ export default async function totpRoutes(
fastify.post(
"/required",
{
preHandler: [requireAuth, requirePermission("settings.security")],
preHandler: [requireAuth, requirePermission("settings.manage")],
bodyLimit: 10240,
},
async (request, reply) => {