This commit is contained in:
seb
2026-11-01 21:16:07 +01:00
parent 833cee5040
commit 518106ede2
3 changed files with 41 additions and 13 deletions

View File

@@ -14,6 +14,7 @@ import {
parseFullHash, parseFullHash,
articleErrorStatus, articleErrorStatus,
interpolateString, interpolateString,
withMandatedOptionsPrice,
interpolateObjects, interpolateObjects,
} from './shared.js'; } from './shared.js';
@@ -408,7 +409,7 @@ export function register(app, basePath = '') {
let article; let article;
try { try {
const result = await db.getBySku(req.user.id, sku); const result = await db.getBySku(req.user.id, sku);
article = result?.article; article = result?.article ? await withMandatedOptionsPrice(req.user.id, result.article) : undefined;
} catch (err) { } catch (err) {
const status = articleErrorStatus(err); const status = articleErrorStatus(err);
if (status === 500) console.error('Get article by sku failed for print:', err); if (status === 500) console.error('Get article by sku failed for print:', err);

View File

@@ -1,4 +1,5 @@
import { emit } from '../../sse.js'; import { emit } from '../../sse.js';
import * as articleDb from '../../../db/article.js';
// #region Helpers // #region Helpers
@@ -133,18 +134,42 @@ const BASEPRICE_FORMATTER = new Intl.NumberFormat('de-DE', {
}); });
/** Formats the price per base unit, e.g. "€ 4,05 / L". Empty when unit data or price is missing. */ /** Formats the price per base unit, e.g. "€ 4,05 / L". Empty when unit data or price is missing. */
export function formatBasePrice(net, taxRate, article) { export function formatBasePrice(grossRaw, article) {
const unit = article?.unit; const unit = article?.unit;
const unitamount = Number(article?.unitamount); const unitamount = Number(article?.unitamount);
const factor = Number(unit?.basesifactor); const factor = Number(unit?.basesifactor);
if (net == null || !unit || !(unitamount > 0) || !(factor > 0)) return ''; if (grossRaw == null || !unit?.basesi || !(unitamount > 0) || !(factor > 0)) return '';
const baseAmount = unitamount * factor; const baseAmount = unitamount * factor;
const perBase = (net * (1 + taxRate / 100)) / baseAmount; // Price per base unit equals the price itself: nothing to add.
const label = factor === 1 ? (unit.symbol || unit.basesi) : unit.basesi; if (Math.abs(baseAmount - 1) < 1e-9) return '';
if (!label) return ''; const perBase = grossRaw / baseAmount;
const label = factor === 1 && unit.symbol ? unit.symbol : unit.basesi;
return `€ ${BASEPRICE_FORMATTER.format(perBase)} / ${label}`; return `€ ${BASEPRICE_FORMATTER.format(perBase)} / ${label}`;
} }
function grossOf(price) {
const net = Number(price?.net);
if (price?.net == null || !Number.isFinite(net)) return null;
const tax = Number(price.taxRate);
return net * (1 + (Number.isFinite(tax) ? tax : 0) / 100);
}
/**
* Returns the article with `mandatedOptionsGross` set to the summed gross price
* (price × count) of its mandated options, for label placeholders.
*/
export async function withMandatedOptionsPrice(userId, article) {
const mandated = Array.isArray(article?.options) ? article.options.filter((o) => o.mandated) : [];
if (mandated.length === 0) return article;
let extra = 0;
for (const opt of mandated) {
const result = await articleDb.getBySku(userId, opt.sku);
const gross = grossOf(result?.article?.prices?.[0]);
if (gross != null) extra += gross * (Number(opt.count) || 1);
}
return { ...article, mandatedOptionsGross: extra };
}
export function interpolateString(str, article) { export function interpolateString(str, article) {
if (typeof str !== 'string') return str; if (typeof str !== 'string') return str;
const basePrice = Array.isArray(article?.prices) && article.prices.length > 0 const basePrice = Array.isArray(article?.prices) && article.prices.length > 0
@@ -156,13 +181,15 @@ export function interpolateString(str, article) {
const taxRate = basePrice?.taxRate != null && Number.isFinite(Number(basePrice.taxRate)) const taxRate = basePrice?.taxRate != null && Number.isFinite(Number(basePrice.taxRate))
? Number(basePrice.taxRate) ? Number(basePrice.taxRate)
: 0; : 0;
const gross = net != null const extra = Number(article?.mandatedOptionsGross) || 0;
? Math.round((net * (1 + taxRate / 100) + Number.EPSILON) * 100) / 100 const grossRaw = net != null ? net * (1 + taxRate / 100) + extra : null;
const gross = grossRaw != null
? Math.round((grossRaw + Number.EPSILON) * 100) / 100
: null; : null;
return str return str
.replace(/\{\{name\}\}/gi, article?.name || '') .replace(/\{\{name\}\}/gi, article?.name || '')
.replace(/\{\{baseprice\}\}/gi, formatBasePrice(net, taxRate, article)) .replace(/\{\{baseprice\}\}/gi, formatBasePrice(grossRaw, article))
.replace(/\{\{price\}\}/gi, formatPrice(gross)); .replace(/\{\{price\}\}/gi, formatPrice(gross));
} }

View File

@@ -8,7 +8,7 @@ import { pngToZpl } from '../../services/zplService.js';
import { notifyAgentJob } from '../agent/index.js'; import { notifyAgentJob } from '../agent/index.js';
import { emit } from '../sse.js'; import { emit } from '../sse.js';
import { applyZplCopies } from './zpljob.js'; import { applyZplCopies } from './zpljob.js';
import { interpolateString, interpolateObjects } from './article/shared.js'; import { interpolateString, interpolateObjects, withMandatedOptionsPrice } from './article/shared.js';
// #region Helpers // #region Helpers
@@ -102,7 +102,7 @@ export function register(app, basePath = '') {
const skuStr = String(req.query.sku).trim(); const skuStr = String(req.query.sku).trim();
const result = await articleDb.getBySku(req.user.id, skuStr); const result = await articleDb.getBySku(req.user.id, skuStr);
if (result?.article) { if (result?.article) {
article = result.article; article = await withMandatedOptionsPrice(req.user.id, result.article);
} }
} }
@@ -220,7 +220,7 @@ export function register(app, basePath = '') {
: null; : null;
if (objects && objects.length > 0) { if (objects && objects.length > 0) {
try { try {
const interpolated = interpolateObjects(objects, result.article); const interpolated = interpolateObjects(objects, await withMandatedOptionsPrice(req.user.id, result.article));
const thumbBuffer = await renderThumb({ const thumbBuffer = await renderThumb({
width: template.width, width: template.width,
height: template.height, height: template.height,
@@ -655,7 +655,7 @@ export function register(app, basePath = '') {
if (!result?.article) { if (!result?.article) {
return res.status(404).json({ error: 'Article not found' }); return res.status(404).json({ error: 'Article not found' });
} }
article = result.article; article = await withMandatedOptionsPrice(req.user.id, result.article);
} else { } else {
article = { article = {
name: template?.name || 'Musterartikel', name: template?.name || 'Musterartikel',