u
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
parseFullHash,
|
||||
articleErrorStatus,
|
||||
interpolateString,
|
||||
withMandatedOptionsPrice,
|
||||
interpolateObjects,
|
||||
} from './shared.js';
|
||||
|
||||
@@ -408,7 +409,7 @@ export function register(app, basePath = '') {
|
||||
let article;
|
||||
try {
|
||||
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) {
|
||||
const status = articleErrorStatus(err);
|
||||
if (status === 500) console.error('Get article by sku failed for print:', err);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { emit } from '../../sse.js';
|
||||
import * as articleDb from '../../../db/article.js';
|
||||
|
||||
// #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. */
|
||||
export function formatBasePrice(net, taxRate, article) {
|
||||
export function formatBasePrice(grossRaw, article) {
|
||||
const unit = article?.unit;
|
||||
const unitamount = Number(article?.unitamount);
|
||||
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 perBase = (net * (1 + taxRate / 100)) / baseAmount;
|
||||
const label = factor === 1 ? (unit.symbol || unit.basesi) : unit.basesi;
|
||||
if (!label) return '';
|
||||
// Price per base unit equals the price itself: nothing to add.
|
||||
if (Math.abs(baseAmount - 1) < 1e-9) return '';
|
||||
const perBase = grossRaw / baseAmount;
|
||||
const label = factor === 1 && unit.symbol ? unit.symbol : unit.basesi;
|
||||
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) {
|
||||
if (typeof str !== 'string') return str;
|
||||
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))
|
||||
? Number(basePrice.taxRate)
|
||||
: 0;
|
||||
const gross = net != null
|
||||
? Math.round((net * (1 + taxRate / 100) + Number.EPSILON) * 100) / 100
|
||||
const extra = Number(article?.mandatedOptionsGross) || 0;
|
||||
const grossRaw = net != null ? net * (1 + taxRate / 100) + extra : null;
|
||||
const gross = grossRaw != null
|
||||
? Math.round((grossRaw + Number.EPSILON) * 100) / 100
|
||||
: null;
|
||||
|
||||
return str
|
||||
.replace(/\{\{name\}\}/gi, article?.name || '')
|
||||
.replace(/\{\{baseprice\}\}/gi, formatBasePrice(net, taxRate, article))
|
||||
.replace(/\{\{baseprice\}\}/gi, formatBasePrice(grossRaw, article))
|
||||
.replace(/\{\{price\}\}/gi, formatPrice(gross));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { pngToZpl } from '../../services/zplService.js';
|
||||
import { notifyAgentJob } from '../agent/index.js';
|
||||
import { emit } from '../sse.js';
|
||||
import { applyZplCopies } from './zpljob.js';
|
||||
import { interpolateString, interpolateObjects } from './article/shared.js';
|
||||
import { interpolateString, interpolateObjects, withMandatedOptionsPrice } from './article/shared.js';
|
||||
|
||||
// #region Helpers
|
||||
|
||||
@@ -102,7 +102,7 @@ export function register(app, basePath = '') {
|
||||
const skuStr = String(req.query.sku).trim();
|
||||
const result = await articleDb.getBySku(req.user.id, skuStr);
|
||||
if (result?.article) {
|
||||
article = result.article;
|
||||
article = await withMandatedOptionsPrice(req.user.id, result.article);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ export function register(app, basePath = '') {
|
||||
: null;
|
||||
if (objects && objects.length > 0) {
|
||||
try {
|
||||
const interpolated = interpolateObjects(objects, result.article);
|
||||
const interpolated = interpolateObjects(objects, await withMandatedOptionsPrice(req.user.id, result.article));
|
||||
const thumbBuffer = await renderThumb({
|
||||
width: template.width,
|
||||
height: template.height,
|
||||
@@ -655,7 +655,7 @@ export function register(app, basePath = '') {
|
||||
if (!result?.article) {
|
||||
return res.status(404).json({ error: 'Article not found' });
|
||||
}
|
||||
article = result.article;
|
||||
article = await withMandatedOptionsPrice(req.user.id, result.article);
|
||||
} else {
|
||||
article = {
|
||||
name: template?.name || 'Musterartikel',
|
||||
|
||||
Reference in New Issue
Block a user