const PRICE_LOCALE = 'de-DE'; export function getBrutto(netto, kSteuerklasse) { const rate = { 1: 19, 2: 7, 3: 0 }[kSteuerklasse] ?? 19; return netto * (1 + rate / 100); } /** Currency display, e.g. `6,60 €`. */ export function formatPrice(amount) { if (amount == null || !Number.isFinite(Number(amount))) return ''; return new Intl.NumberFormat(PRICE_LOCALE, { style: 'currency', currency: 'EUR', }).format(amount); } /** * Amount without currency symbol (for fields with a separate € adornment). * e.g. `6,60` / `1.234,56` */ export function formatPriceAmount(amount, { minFractionDigits = 2, maxFractionDigits = 2 } = {}) { if (amount == null || !Number.isFinite(Number(amount))) return ''; return new Intl.NumberFormat(PRICE_LOCALE, { minimumFractionDigits: minFractionDigits, maximumFractionDigits: maxFractionDigits, }).format(amount); } /** * Parse de-DE (or plain) price input → number. * Accepts `6,60`, `1.234,56`, and plain `6.60`. */ export function parsePriceInput(value) { if (value === '' || value == null) return null; let normalized = String(value).trim().replace(/\s/g, ''); if (normalized === '') return null; if (normalized.includes(',')) { // de-DE: thousand `.`, decimal `,` normalized = normalized.replace(/\./g, '').replace(',', '.'); } const parsed = Number(normalized); return Number.isFinite(parsed) && parsed >= 0 ? parsed : null; } /** Convert gross (brutto) → net for PUT /prices. Rounded to 4 decimal places. */ export function netFromGross(gross, taxRate) { if (gross == null || taxRate == null) return null; if (!Number.isFinite(gross) || !Number.isFinite(taxRate) || taxRate < 0) return null; const factor = 1 + taxRate / 100; if (factor <= 0) return null; return Math.round((gross / factor) * 10000) / 10000; } /** * Resolves or calculates the gross price (Bruttopreis) for an article. * Supports: * - Direct gross price (`article.gross`) * - Net price with tax rate (`article.net`, `article.taxRate`) * - Nested prices array (`article.prices`) * * @param {object|null} article * @param {number} [defaultTaxRate=0] Fallback tax rate if not present on article/price * @returns {number|null} Gross price rounded to 2 decimal places, or null */ export function getArticleGross(article, defaultTaxRate = 0) { if (!article) return null; if (article.gross != null && Number.isFinite(Number(article.gross))) { return Number(article.gross); } let net = article.net ?? article.price ?? article.vknetto; let taxRate = article.taxRate; if (net == null && Array.isArray(article.prices) && article.prices.length > 0) { const base = article.prices.find((p) => p.minmultiplier === 1) ?? article.prices[0]; if (base?.gross != null && Number.isFinite(Number(base.gross))) { return Number(base.gross); } net = base?.net; if (taxRate == null) taxRate = base?.taxRate; } if (net == null || !Number.isFinite(Number(net))) return null; const rate = taxRate != null && Number.isFinite(Number(taxRate)) ? Number(taxRate) : Number(defaultTaxRate ?? 0); const gross = Number(net) * (1 + rate / 100); return Math.round((gross + Number.EPSILON) * 100) / 100; }