feat(crm): backend code — services + routes + utilities + tests
Brings in the full backend CRM stack on top of the consolidated
migration (60abe8c).
Services (CRM)
- quoteService — full lifecycle (draft → sent → accepted → converted
to event/invoice), Skonto + Storno + reissue paths
- invoiceService — spawnInstallmentInvoices, updateInstallmentPlan,
monthly-billing accumulator, payment-check tokens, dunning ladder
- contractService — block-composable contract editor, in-browser
signature flow, wet-PDF upload path, integrity check, audit trail
- customerHoursService — per-entry locking, billing integration
- dealsService — cross-document lineage (deal_uuid)
- taxReportService — quarterly aggregates + CSV/PDF export
- eventReminderService — pre-event customer reminder cron pass
- _renderContext — shared issuer/recipient blocks across PDF types
- pdfService extensions — custom-font registration, font picker
Routes (admin + public)
- adminQuotes, adminInvoices, adminContracts, adminCalendar,
adminDeals, adminTaxReport, adminDev, adminBusinessProfile
- publicQuotes (accept/decline), publicContracts (sign),
publicPaymentCheck
- Extensions on adminEvents, adminCustomers, adminSettings,
adminEmail, adminFeatureFlags, adminThumbnails, adminPhotos,
adminCategories, adminUsers, adminArchives, adminDashboard
- server.js wires the new mounts (kept upstream's noStoreCache on
customer routes per 3-way merge)
Utilities
- schemaCache (cached hasColumn lookups across services)
- documentSequences (atomic gap-free numbering — §14 UStG)
- safePath (path-containment guards at fs stream boundaries)
- clientIp (sanctioned XFF reader for audit logs)
- publicTokenGuards (pre-multer token validation + attempt counters)
- numericHelpers (ensureInt / ensureNumber consolidation)
- dateFormatter (formatShortDate + dateInputLang)
- dbCompat extensions, iban + pdfFilename helpers, resolveLogoFile
Infrastructure
- Bundled PDF fonts (Comic-Neue / IBM-Plex-Sans / Inter / Jost /
Montserrat / Noto-Sans / Playfair-Display / Poppins)
- Backend package.json + lock updates (pdfkit, signature_pad,
qrcode, et al.)
- Sample storage layout under storage/business-docs/quote/
Tests
- 14 new test files covering quote/invoice/contract lifecycle,
installment plan reshape, line-item hierarchy, customer hours,
payment check, tax report PDF, IBAN parsing, filename sanitiser
This commit is contained in:
+49
-4
@@ -20,6 +20,7 @@ const path = require('path');
|
||||
const { initializeDatabase, db } = require('./src/database/db');
|
||||
const { startFileWatcher } = require('./src/services/fileWatcher');
|
||||
const { startExpirationChecker } = require('./src/services/expirationChecker');
|
||||
const { startInvoiceScheduler } = require('./src/services/invoiceSchedulerService');
|
||||
const { initializeTransporter, startEmailQueueProcessor } = require('./src/services/emailProcessor');
|
||||
const { startBackupService } = require('./src/services/backupService');
|
||||
const { startScheduledBackups } = require('./src/services/databaseBackup');
|
||||
@@ -46,9 +47,28 @@ const secureImagesRoutes = require('./src/routes/secureImages');
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// Trust proxy headers (required for Traefik/nginx)
|
||||
// Set to specific number of proxies or loopback to be more secure
|
||||
app.set('trust proxy', 'loopback, linklocal, uniquelocal');
|
||||
// Trust proxy headers (required for Traefik/nginx).
|
||||
//
|
||||
// `req.ip` is computed by Express by walking X-Forwarded-For from
|
||||
// right-to-left and stopping at the first hop NOT in this list, so
|
||||
// the value picpeak audits (signing IPs, payment-check actions,
|
||||
// rate-limit keys) is the originating client IP behind any number
|
||||
// of trusted reverse proxies.
|
||||
//
|
||||
// Default: 'loopback, linklocal, uniquelocal' — covers localhost,
|
||||
// link-local (169.254.0.0/16), and unique-local IPv6 (fc00::/7).
|
||||
// Standard for nginx-in-front-of-Node deployments on the same host
|
||||
// and for Docker bridge networks. Operators with unusual topologies
|
||||
// (load balancer in a public subnet, multi-hop NAT) override via
|
||||
// TRUST_PROXY env, accepting any value Express accepts: a number,
|
||||
// 'loopback', 'linklocal', 'uniquelocal', a CIDR, a comma list, or
|
||||
// 'true' (trust ALL proxies — only safe behind a fully-controlled
|
||||
// reverse-proxy chain).
|
||||
//
|
||||
// NEVER read req.headers['x-forwarded-for'] directly in audit paths
|
||||
// — see utils/clientIp.js for the rationale.
|
||||
const trustProxySetting = process.env.TRUST_PROXY || 'loopback, linklocal, uniquelocal';
|
||||
app.set('trust proxy', trustProxySetting === 'true' ? true : trustProxySetting);
|
||||
|
||||
// Security middleware with custom CSP
|
||||
// In native HTTP installs, do NOT force HTTPS for subresources.
|
||||
@@ -620,9 +640,30 @@ app.use('/api/admin/users', require('./src/routes/adminUsers'));
|
||||
const { noStoreCache } = require('./src/middleware/noStoreCache');
|
||||
app.use('/api/admin/customers', noStoreCache, require('./src/routes/adminCustomers'));
|
||||
// Customer-side surface (#354). Strictly separate from /api/admin/* —
|
||||
// distinct token type, distinct cookie, distinct middleware.
|
||||
// distinct token type, distinct cookie, distinct middleware. The
|
||||
// noStoreCache wrapper (upstream) prevents stale customer-portal
|
||||
// data from being served after logout. The CRM-area route-flag
|
||||
// gate was reverted upstream and lives in the UI now.
|
||||
app.use('/api/customer/auth', noStoreCache, require('./src/routes/customerAuth'));
|
||||
app.use('/api/customer', noStoreCache, require('./src/routes/customer'));
|
||||
|
||||
// --- CRM (#TBD) -------------------------------------------------------
|
||||
// Quotes / Invoices / Contracts / Calendar / Tax report / Deals lineage.
|
||||
// Business profile (issuer block for PDFs) lives at
|
||||
// /api/admin/business-profile, gated by the existing settings.manage
|
||||
// permission rather than a CRM-specific one. The public endpoints
|
||||
// host the customer-side accept/decline / sign / payment-check pages.
|
||||
app.use('/api/admin/business-profile', require('./src/routes/adminBusinessProfile'));
|
||||
app.use('/api/admin/quotes', require('./src/routes/adminQuotes'));
|
||||
app.use('/api/admin/invoices', require('./src/routes/adminInvoices'));
|
||||
app.use('/api/admin/contracts', require('./src/routes/adminContracts'));
|
||||
app.use('/api/admin/calendar', require('./src/routes/adminCalendar'));
|
||||
app.use('/api/admin/deals', require('./src/routes/adminDeals'));
|
||||
app.use('/api/admin/tax-report', require('./src/routes/adminTaxReport'));
|
||||
app.use('/api/admin/dev', require('./src/routes/adminDev'));
|
||||
app.use('/api/public/quotes', require('./src/routes/publicQuotes'));
|
||||
app.use('/api/public/contracts', require('./src/routes/publicContracts'));
|
||||
app.use('/api/public/payment-check', require('./src/routes/publicPaymentCheck'));
|
||||
app.use('/api/admin/event-types', require('./src/routes/adminEventTypes'));
|
||||
app.use('/api/admin/api-tokens', require('./src/routes/adminApiTokens'));
|
||||
app.use('/api/admin/webhooks', require('./src/routes/adminWebhooks'));
|
||||
@@ -729,6 +770,10 @@ async function startServer() {
|
||||
|
||||
// Start expiration checker
|
||||
startExpirationChecker();
|
||||
// CRM invoice scheduler: hourly tick to flush scheduled-send invoices
|
||||
// + run the overdue reminder ladder. No-op when the `bills` feature
|
||||
// flag is OFF (the service short-circuits on empty result sets).
|
||||
startInvoiceScheduler();
|
||||
|
||||
// Initialize email transporter and start queue processor
|
||||
await initializeTransporter();
|
||||
|
||||
Reference in New Issue
Block a user