Compare commits
2 Commits
aeb79c1acb
...
518106ede2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
518106ede2 | ||
|
|
833cee5040 |
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -127,6 +128,48 @@ export function formatPrice(amount) {
|
|||||||
return CURRENCY_FORMATTER.format(Number(amount));
|
return CURRENCY_FORMATTER.format(Number(amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BASEPRICE_FORMATTER = new Intl.NumberFormat('de-DE', {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Formats the price per base unit, e.g. "€ 4,05 / L". Empty when unit data or price is missing. */
|
||||||
|
export function formatBasePrice(grossRaw, article) {
|
||||||
|
const unit = article?.unit;
|
||||||
|
const unitamount = Number(article?.unitamount);
|
||||||
|
const factor = Number(unit?.basesifactor);
|
||||||
|
if (grossRaw == null || !unit?.basesi || !(unitamount > 0) || !(factor > 0)) return '';
|
||||||
|
const baseAmount = unitamount * factor;
|
||||||
|
// 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) {
|
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
|
||||||
@@ -138,12 +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(grossRaw, article))
|
||||||
.replace(/\{\{price\}\}/gi, formatPrice(gross));
|
.replace(/\{\{price\}\}/gi, formatPrice(gross));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user