Files
picpeak/backend/src/utils/numericHelpers.js
T
Luca d543949188 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
2026-05-26 18:18:51 +02:00

35 lines
1.2 KiB
JavaScript

/**
* Shared numeric coercion helpers used across the CRM services.
*
* Previously: 4 copies of `ensureInt` + 2 copies of `ensureNumber` lived
* across quoteService, invoiceService, contractService, and
* taxReportService. Each copy was identical apart from `Number.isFinite`
* vs `!Number.isNaN` — converging on the same answer in practice
* because `parseInt`/`Number` never produce `Infinity` from string input.
*
* One canonical pair lives here so future numeric coercion concerns
* (e.g. BigInt safety, locale-aware decimals) are addressed in one place.
*/
/**
* Coerce a value to a non-NaN integer, defaulting to 0 on garbage.
* Matches the legacy `ensureInt` semantics across all four services.
*/
function ensureInt(value) {
const n = parseInt(value, 10);
return Number.isFinite(n) ? n : 0;
}
/**
* Coerce a value to a finite Number, defaulting to `fallback` (0) on
* null/undefined/empty string/NaN. Matches the legacy `ensureNumber`
* shape used by quote + invoice line-item math.
*/
function ensureNumber(value, fallback = 0) {
if (value === null || value === undefined || value === '') return fallback;
const n = Number(value);
return Number.isFinite(n) ? n : fallback;
}
module.exports = { ensureInt, ensureNumber };