@softwear/latestcollectioncore 1.0.191 → 1.0.194

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -19,6 +19,8 @@ export { default as reports, genPDF } from './reports';
19
19
  export { default as round2 } from './round2';
20
20
  export { default as sizeToMap } from './sizeToMap';
21
21
  export { default as transaction } from './transaction';
22
+ export type { LayoutFieldFormat } from './layoutFieldFormat';
23
+ export { isCurrencyFieldFormat, isZeroFieldValue, resolveLayoutFieldText, shouldSuppressZeroField } from './layoutFieldFormat';
22
24
  export * from './pdf';
23
25
  export type { GenPdfAlert, GenPdfOptions, PdfFontDefinition, PdfFontVariant, PdfImageAsset } from './reports';
24
26
  export * from './types';
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.isAxiosError = exports.exponentialDelay = exports.axiosRetry = exports.createAxiosInstance = exports.lcAxios = exports.transaction = exports.sizeToMap = exports.round2 = exports.genPDF = exports.reports = exports.projectObject = exports.pivotTable = exports.isean13 = exports.imageBinder = exports.hasOnlyDigits = exports.hashBrand = exports.getPreferedPropertyMappings = exports.getBrandName = exports.findMetaBrandSetting = exports.findSkuByBarcode = exports.ensureImpliedProperties = exports.ensureArray = exports.edifact = exports.ean13 = exports.deepCopy = exports.buildPropertyMappingFn = exports.articleStatus = void 0;
20
+ exports.isAxiosError = exports.exponentialDelay = exports.axiosRetry = exports.createAxiosInstance = exports.lcAxios = exports.shouldSuppressZeroField = exports.resolveLayoutFieldText = exports.isZeroFieldValue = exports.isCurrencyFieldFormat = exports.transaction = exports.sizeToMap = exports.round2 = exports.genPDF = exports.reports = exports.projectObject = exports.pivotTable = exports.isean13 = exports.imageBinder = exports.hasOnlyDigits = exports.hashBrand = exports.getPreferedPropertyMappings = exports.getBrandName = exports.findMetaBrandSetting = exports.findSkuByBarcode = exports.ensureImpliedProperties = exports.ensureArray = exports.edifact = exports.ean13 = exports.deepCopy = exports.buildPropertyMappingFn = exports.articleStatus = void 0;
21
21
  var articleStatus_1 = require("./articleStatus");
22
22
  Object.defineProperty(exports, "articleStatus", { enumerable: true, get: function () { return __importDefault(articleStatus_1).default; } });
23
23
  var buildPropertyMappingFn_1 = require("./buildPropertyMappingFn");
@@ -61,6 +61,11 @@ var sizeToMap_1 = require("./sizeToMap");
61
61
  Object.defineProperty(exports, "sizeToMap", { enumerable: true, get: function () { return __importDefault(sizeToMap_1).default; } });
62
62
  var transaction_1 = require("./transaction");
63
63
  Object.defineProperty(exports, "transaction", { enumerable: true, get: function () { return __importDefault(transaction_1).default; } });
64
+ var layoutFieldFormat_1 = require("./layoutFieldFormat");
65
+ Object.defineProperty(exports, "isCurrencyFieldFormat", { enumerable: true, get: function () { return layoutFieldFormat_1.isCurrencyFieldFormat; } });
66
+ Object.defineProperty(exports, "isZeroFieldValue", { enumerable: true, get: function () { return layoutFieldFormat_1.isZeroFieldValue; } });
67
+ Object.defineProperty(exports, "resolveLayoutFieldText", { enumerable: true, get: function () { return layoutFieldFormat_1.resolveLayoutFieldText; } });
68
+ Object.defineProperty(exports, "shouldSuppressZeroField", { enumerable: true, get: function () { return layoutFieldFormat_1.shouldSuppressZeroField; } });
64
69
  __exportStar(require("./pdf"), exports);
65
70
  __exportStar(require("./types"), exports);
66
71
  __exportStar(require("./consts"), exports);
@@ -0,0 +1,16 @@
1
+ export type LayoutFieldFormat = 'text' | 'number' | 'date' | 'currency' | 'number-nonzero' | 'currency-nonzero';
2
+ export interface ResolveLayoutFieldTextOptions {
3
+ pageNumber?: number;
4
+ pageCount?: number;
5
+ formatDate: (raw: number | string) => string;
6
+ formatCurrency?: (raw: string) => string;
7
+ onFieldNotFound?: (source: string) => void;
8
+ }
9
+ export declare function isZeroFieldValue(value: unknown): boolean;
10
+ export declare function isCurrencyFieldFormat(format: string | undefined): boolean;
11
+ export declare function shouldSuppressZeroField(format: string | undefined): boolean;
12
+ export declare function resolveLayoutFieldText(object: {
13
+ format?: string;
14
+ source?: string;
15
+ type?: string;
16
+ }, printBuffer: any, getProperty: (propertyName: string, object: any) => any, options: ResolveLayoutFieldTextOptions): string;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveLayoutFieldText = exports.shouldSuppressZeroField = exports.isCurrencyFieldFormat = exports.isZeroFieldValue = void 0;
4
+ function isZeroFieldValue(value) {
5
+ if (value === 0 || value === '0')
6
+ return true;
7
+ if (typeof value === 'number')
8
+ return value === 0;
9
+ if (typeof value !== 'string')
10
+ return false;
11
+ const trimmed = value.trim();
12
+ if (trimmed === '0')
13
+ return true;
14
+ const normalized = trimmed.replace(',', '.');
15
+ const parsed = parseFloat(normalized);
16
+ return trimmed !== '' && !Number.isNaN(parsed) && parsed === 0;
17
+ }
18
+ exports.isZeroFieldValue = isZeroFieldValue;
19
+ function isCurrencyFieldFormat(format) {
20
+ return format === 'currency' || format === 'currency-nonzero';
21
+ }
22
+ exports.isCurrencyFieldFormat = isCurrencyFieldFormat;
23
+ function shouldSuppressZeroField(format) {
24
+ return format === 'currency-nonzero' || format === 'number-nonzero';
25
+ }
26
+ exports.shouldSuppressZeroField = shouldSuppressZeroField;
27
+ function resolveLayoutFieldText(object, printBuffer, getProperty, options) {
28
+ var _a, _b, _c, _d, _e;
29
+ if (object.type !== 'field')
30
+ return '';
31
+ let raw;
32
+ if (object.source === 'pageNumber')
33
+ raw = String((_a = options.pageNumber) !== null && _a !== void 0 ? _a : '');
34
+ else if (object.source === 'pageCount')
35
+ raw = String((_b = options.pageCount) !== null && _b !== void 0 ? _b : 0);
36
+ else if (object.source === 'page')
37
+ raw = `${(_c = options.pageNumber) !== null && _c !== void 0 ? _c : ''} / ${(_d = options.pageCount) !== null && _d !== void 0 ? _d : 0}`;
38
+ else if (object.source && printBuffer !== undefined && printBuffer !== null)
39
+ raw = getProperty(object.source, printBuffer);
40
+ else
41
+ raw = printBuffer;
42
+ if (shouldSuppressZeroField(object.format) && isZeroFieldValue(raw))
43
+ return '';
44
+ let text;
45
+ if (typeof raw === 'number')
46
+ text = String(raw);
47
+ else if (raw === undefined || raw === null) {
48
+ (_e = options.onFieldNotFound) === null || _e === void 0 ? void 0 : _e.call(options, object.source || '');
49
+ return '';
50
+ }
51
+ else
52
+ text = String(raw);
53
+ if (object.format === 'date')
54
+ text = options.formatDate(parseInt(text));
55
+ if (isCurrencyFieldFormat(object.format)) {
56
+ text = options.formatCurrency ? options.formatCurrency(text) : '€ ' + parseFloat(text).toFixed(2);
57
+ }
58
+ return text;
59
+ }
60
+ exports.resolveLayoutFieldText = resolveLayoutFieldText;
package/dist/pdf.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { LayoutFieldFormat } from './layoutFieldFormat';
1
2
  export interface PaperSize {
2
3
  width: number;
3
4
  height: number;
@@ -28,7 +29,7 @@ export interface TextLayoutObject extends BaseLayoutObject {
28
29
  export interface FieldLayoutObject extends BaseLayoutObject {
29
30
  type: 'field';
30
31
  source?: string;
31
- format?: 'date' | 'currency';
32
+ format?: LayoutFieldFormat;
32
33
  textAlign?: TextAlign;
33
34
  fontFamily: string;
34
35
  fontSize: number;
package/dist/reports.d.ts CHANGED
@@ -32,6 +32,8 @@ export interface GenPdfOptions {
32
32
  resolveLogoUrl?: () => string | undefined;
33
33
  isCustomPdfFont?: (fontFamily: string) => boolean;
34
34
  getPdfFontDefinition?: (fontFamily: string) => PdfFontDefinition | undefined;
35
+ /** Load TTF bytes as base64 (e.g. from disk in Node). Default: fetch variant.url. */
36
+ loadFont?: (url: string) => Promise<string>;
35
37
  loadImage?: (url: string) => Promise<PdfImageAsset | undefined>;
36
38
  }
37
39
  export declare function genPDF(layout: Layout, printBuffer: any, options?: GenPdfOptions): Promise<jsPDF>;
package/dist/reports.js CHANGED
@@ -16,6 +16,7 @@ exports.genPDF = void 0;
16
16
  const jspdf_1 = require("jspdf");
17
17
  const date_fns_1 = require("date-fns");
18
18
  const deepCopy_1 = __importDefault(require("./deepCopy"));
19
+ const layoutFieldFormat_1 = require("./layoutFieldFormat");
19
20
  /** Measurement uses context `currentPageNumber` (1-based). Rendering uses jsPDF’s `getCurrentPageInfo().pageNumber`, which is already 1-based (first page is 1). */
20
21
  function getCurrentPageNumber(doc, context) {
21
22
  var _a;
@@ -245,9 +246,10 @@ function embedFontsInDoc(doc, usedFontFamilies, options) {
245
246
  if (def.variants.length === 0) {
246
247
  continue;
247
248
  }
249
+ const loadFont = options.loadFont || fetchFontAsBase64;
248
250
  for (const variant of def.variants) {
249
251
  const vfsName = `${family}-${variant.style}.ttf`;
250
- const base64 = yield fetchFontAsBase64(variant.url);
252
+ const base64 = yield loadFont(variant.url);
251
253
  doc.addFileToVFS(vfsName, base64);
252
254
  doc.addFont(vfsName, family, variant.style, 'Identity-H');
253
255
  }
@@ -268,7 +270,7 @@ function truncateTextToFitWidth(doc, text, maxWidth) {
268
270
  return truncatedText + ELLIPSIS;
269
271
  }
270
272
  function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options, context) {
271
- var _a, _b, _c;
273
+ var _a, _b;
272
274
  let text = ''; // Will hold text to display for either 'text' or 'field'
273
275
  // Get text from object for text objects
274
276
  if (object.type == 'text') {
@@ -276,33 +278,16 @@ function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options
276
278
  }
277
279
  // Get text from printBuffer for field objects
278
280
  if (object.type == 'field') {
279
- if (object.source === 'pageNumber') {
280
- text = String(doc.getNumberOfPages());
281
- }
282
- else if (object.source === 'pageCount') {
283
- text = String(context.pageCount || 0);
284
- }
285
- else if (object.source === 'page') {
286
- text = String(doc.getNumberOfPages()) + ' / ' + String(context.pageCount || 0);
287
- }
288
- else if (object.source && printBuffer) {
289
- text = getProperty(object.source, printBuffer);
290
- }
291
- else {
292
- text = printBuffer;
293
- }
294
- if (typeof text == 'number')
295
- text = '' + text;
296
- if (object.format == 'date')
297
- text = formatDate(parseInt(text));
298
- if (text === undefined || typeof text != 'string') {
299
- (_a = options.onAlert) === null || _a === void 0 ? void 0 : _a.call(options, { header: 'fieldnotfound', body: object.source, type: 'warning', timeout: 10000 });
300
- text = '';
301
- }
302
- if (object.format == 'currency') {
303
- const raw = text;
304
- text = options.formatCurrency ? options.formatCurrency(raw) : '€ ' + parseFloat(raw).toFixed(2);
305
- }
281
+ text = (0, layoutFieldFormat_1.resolveLayoutFieldText)(object, printBuffer, getProperty, {
282
+ pageNumber: doc.getNumberOfPages(),
283
+ pageCount: context.pageCount || 0,
284
+ formatDate,
285
+ formatCurrency: options.formatCurrency,
286
+ onFieldNotFound: (source) => {
287
+ var _a;
288
+ (_a = options.onAlert) === null || _a === void 0 ? void 0 : _a.call(options, { header: 'fieldnotfound', body: source, type: 'warning', timeout: 10000 });
289
+ },
290
+ });
306
291
  }
307
292
  // Manipulate case as specified in object
308
293
  if ((object.type === 'text' || object.type === 'field') && object.case === 1)
@@ -364,7 +349,7 @@ function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options
364
349
  const imgUrl = resolveImageUrl(object, printBuffer);
365
350
  if (imgUrl) {
366
351
  const urlLower = String(imgUrl).toLowerCase();
367
- const imageAsset = (_b = context.imageAssets) === null || _b === void 0 ? void 0 : _b.get(String(imgUrl));
352
+ const imageAsset = (_a = context.imageAssets) === null || _a === void 0 ? void 0 : _a.get(String(imgUrl));
368
353
  const format = (imageAsset === null || imageAsset === void 0 ? void 0 : imageAsset.format) || (urlLower.endsWith('.png') ? 'PNG' : urlLower.endsWith('.jpg') || urlLower.endsWith('.jpeg') ? 'JPEG' : 'JPEG');
369
354
  let drawX = x;
370
355
  let drawY = y;
@@ -403,7 +388,7 @@ function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options
403
388
  return;
404
389
  const fontStyleStr = FONT_STYLES[object.fontStyle & 3] || 'normal';
405
390
  // Use object font if it's a custom font; otherwise layout default overrides standard fonts (Helvetica etc.)
406
- const fontFamily = object.fontFamily && ((_c = options.isCustomPdfFont) === null || _c === void 0 ? void 0 : _c.call(options, object.fontFamily)) ? object.fontFamily : context.defaultFontFamily || object.fontFamily || 'Helvetica';
391
+ const fontFamily = object.fontFamily && ((_b = options.isCustomPdfFont) === null || _b === void 0 ? void 0 : _b.call(options, object.fontFamily)) ? object.fontFamily : context.defaultFontFamily || object.fontFamily || 'Helvetica';
407
392
  doc.setFont(fontFamily, fontStyleStr);
408
393
  doc.setFontSize(object.fontSize / 4);
409
394
  doc.text(truncateTextToFitWidth(doc, text, width), x, y, { align: textAlign, baseline: 'hanging' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwear/latestcollectioncore",
3
- "version": "1.0.191",
3
+ "version": "1.0.194",
4
4
  "description": "Core functions for LatestCollections applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -17,8 +17,8 @@
17
17
  "scripts": {
18
18
  "build": "tsc -p tsconfig.json",
19
19
  "build:watch": "tsc -p tsconfig.json --watch",
20
- "test": "vitest run",
21
- "test:watch": "vitest"
20
+ "test": "TZ=Europe/Amsterdam vitest run",
21
+ "test:watch": "TZ=Europe/Amsterdam vitest"
22
22
  },
23
23
  "repository": {
24
24
  "type": "git",
package/src/index.ts CHANGED
@@ -19,6 +19,8 @@ export { default as reports, genPDF } from './reports'
19
19
  export { default as round2 } from './round2'
20
20
  export { default as sizeToMap } from './sizeToMap'
21
21
  export { default as transaction } from './transaction'
22
+ export type { LayoutFieldFormat } from './layoutFieldFormat'
23
+ export { isCurrencyFieldFormat, isZeroFieldValue, resolveLayoutFieldText, shouldSuppressZeroField } from './layoutFieldFormat'
22
24
  export * from './pdf'
23
25
  export type { GenPdfAlert, GenPdfOptions, PdfFontDefinition, PdfFontVariant, PdfImageAsset } from './reports'
24
26
  export * from './types'
@@ -0,0 +1,60 @@
1
+ export type LayoutFieldFormat = 'text' | 'number' | 'date' | 'currency' | 'number-nonzero' | 'currency-nonzero'
2
+
3
+ export interface ResolveLayoutFieldTextOptions {
4
+ pageNumber?: number
5
+ pageCount?: number
6
+ formatDate: (raw: number | string) => string
7
+ formatCurrency?: (raw: string) => string
8
+ onFieldNotFound?: (source: string) => void
9
+ }
10
+
11
+ export function isZeroFieldValue(value: unknown): boolean {
12
+ if (value === 0 || value === '0') return true
13
+ if (typeof value === 'number') return value === 0
14
+ if (typeof value !== 'string') return false
15
+ const trimmed = value.trim()
16
+ if (trimmed === '0') return true
17
+ const normalized = trimmed.replace(',', '.')
18
+ const parsed = parseFloat(normalized)
19
+ return trimmed !== '' && !Number.isNaN(parsed) && parsed === 0
20
+ }
21
+
22
+ export function isCurrencyFieldFormat(format: string | undefined): boolean {
23
+ return format === 'currency' || format === 'currency-nonzero'
24
+ }
25
+
26
+ export function shouldSuppressZeroField(format: string | undefined): boolean {
27
+ return format === 'currency-nonzero' || format === 'number-nonzero'
28
+ }
29
+
30
+ export function resolveLayoutFieldText(
31
+ object: { format?: string; source?: string; type?: string },
32
+ printBuffer: any,
33
+ getProperty: (propertyName: string, object: any) => any,
34
+ options: ResolveLayoutFieldTextOptions,
35
+ ): string {
36
+ if (object.type !== 'field') return ''
37
+
38
+ let raw: unknown
39
+ if (object.source === 'pageNumber') raw = String(options.pageNumber ?? '')
40
+ else if (object.source === 'pageCount') raw = String(options.pageCount ?? 0)
41
+ else if (object.source === 'page') raw = `${options.pageNumber ?? ''} / ${options.pageCount ?? 0}`
42
+ else if (object.source && printBuffer !== undefined && printBuffer !== null) raw = getProperty(object.source, printBuffer)
43
+ else raw = printBuffer
44
+
45
+ if (shouldSuppressZeroField(object.format) && isZeroFieldValue(raw)) return ''
46
+
47
+ let text: string
48
+ if (typeof raw === 'number') text = String(raw)
49
+ else if (raw === undefined || raw === null) {
50
+ options.onFieldNotFound?.(object.source || '')
51
+ return ''
52
+ } else text = String(raw)
53
+
54
+ if (object.format === 'date') text = options.formatDate(parseInt(text))
55
+ if (isCurrencyFieldFormat(object.format)) {
56
+ text = options.formatCurrency ? options.formatCurrency(text) : '€ ' + parseFloat(text).toFixed(2)
57
+ }
58
+
59
+ return text
60
+ }
package/src/pdf.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { LayoutFieldFormat } from './layoutFieldFormat'
2
+
1
3
  export interface PaperSize {
2
4
  width: number
3
5
  height: number
@@ -32,7 +34,7 @@ export interface TextLayoutObject extends BaseLayoutObject {
32
34
  export interface FieldLayoutObject extends BaseLayoutObject {
33
35
  type: 'field'
34
36
  source?: string
35
- format?: 'date' | 'currency'
37
+ format?: LayoutFieldFormat
36
38
  textAlign?: TextAlign
37
39
  fontFamily: string
38
40
  fontSize: number
package/src/reports.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { jsPDF } from 'jspdf'
2
2
  import { format } from 'date-fns'
3
3
  import deepCopy from './deepCopy'
4
+ import { resolveLayoutFieldText } from './layoutFieldFormat'
4
5
  import { PaperSize, Layout, LayoutObject, ContainerLayoutObject } from './pdf'
5
6
 
6
7
  type RenderMode = 'measurement' | 'rendering'
@@ -41,6 +42,8 @@ export interface GenPdfOptions {
41
42
  resolveLogoUrl?: () => string | undefined
42
43
  isCustomPdfFont?: (fontFamily: string) => boolean
43
44
  getPdfFontDefinition?: (fontFamily: string) => PdfFontDefinition | undefined
45
+ /** Load TTF bytes as base64 (e.g. from disk in Node). Default: fetch variant.url. */
46
+ loadFont?: (url: string) => Promise<string>
44
47
  loadImage?: (url: string) => Promise<PdfImageAsset | undefined>
45
48
  }
46
49
 
@@ -311,9 +314,10 @@ async function embedFontsInDoc(doc: jsPDF, usedFontFamilies: Set<string>, option
311
314
  if (def.variants.length === 0) {
312
315
  continue
313
316
  }
317
+ const loadFont = options.loadFont || fetchFontAsBase64
314
318
  for (const variant of def.variants) {
315
319
  const vfsName = `${family}-${variant.style}.ttf`
316
- const base64 = await fetchFontAsBase64(variant.url)
320
+ const base64 = await loadFont(variant.url)
317
321
  doc.addFileToVFS(vfsName, base64)
318
322
  doc.addFont(vfsName, family, variant.style, 'Identity-H')
319
323
  }
@@ -342,27 +346,15 @@ function drawSimpleObject(x: number, y: number, doc: jsPDF, object: LayoutObject
342
346
  }
343
347
  // Get text from printBuffer for field objects
344
348
  if (object.type == 'field') {
345
- if (object.source === 'pageNumber') {
346
- text = String(doc.getNumberOfPages())
347
- } else if (object.source === 'pageCount') {
348
- text = String(context.pageCount || 0)
349
- } else if (object.source === 'page') {
350
- text = String(doc.getNumberOfPages()) + ' / ' + String(context.pageCount || 0)
351
- } else if (object.source && printBuffer) {
352
- text = getProperty(object.source, printBuffer)
353
- } else {
354
- text = printBuffer
355
- }
356
- if (typeof text == 'number') text = '' + text
357
- if (object.format == 'date') text = formatDate(parseInt(text))
358
- if (text === undefined || typeof text != 'string') {
359
- options.onAlert?.({ header: 'fieldnotfound', body: object.source, type: 'warning', timeout: 10000 })
360
- text = ''
361
- }
362
- if (object.format == 'currency') {
363
- const raw = text
364
- text = options.formatCurrency ? options.formatCurrency(raw) : '€ ' + parseFloat(raw).toFixed(2)
365
- }
349
+ text = resolveLayoutFieldText(object, printBuffer, getProperty, {
350
+ pageNumber: doc.getNumberOfPages(),
351
+ pageCount: context.pageCount || 0,
352
+ formatDate,
353
+ formatCurrency: options.formatCurrency,
354
+ onFieldNotFound: (source) => {
355
+ options.onAlert?.({ header: 'fieldnotfound', body: source, type: 'warning', timeout: 10000 })
356
+ },
357
+ })
366
358
  }
367
359
  // Manipulate case as specified in object
368
360
  if ((object.type === 'text' || object.type === 'field') && object.case === 1) text = text.toUpperCase()
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { isZeroFieldValue, resolveLayoutFieldText } from '../src/layoutFieldFormat'
3
+
4
+ const getProperty = (propertyName: string, object: any) => object[propertyName]
5
+
6
+ describe('isZeroFieldValue', () => {
7
+ it('detects numeric and string zero values', () => {
8
+ expect(isZeroFieldValue(0)).toBe(true)
9
+ expect(isZeroFieldValue('0')).toBe(true)
10
+ expect(isZeroFieldValue('0.00')).toBe(true)
11
+ expect(isZeroFieldValue('0,00')).toBe(true)
12
+ expect(isZeroFieldValue(' 0.0 ')).toBe(true)
13
+ })
14
+
15
+ it('does not treat empty or non-zero values as zero', () => {
16
+ expect(isZeroFieldValue('')).toBe(false)
17
+ expect(isZeroFieldValue(null)).toBe(false)
18
+ expect(isZeroFieldValue(undefined)).toBe(false)
19
+ expect(isZeroFieldValue('10')).toBe(false)
20
+ expect(isZeroFieldValue(0.01)).toBe(false)
21
+ })
22
+ })
23
+
24
+ describe('resolveLayoutFieldText', () => {
25
+ const baseOptions = {
26
+ formatDate: (raw: number | string) => `DATE:${raw}`,
27
+ formatCurrency: (raw: string) => `CUR:${raw}`,
28
+ }
29
+
30
+ it('suppresses currency-nonzero fields with zero values', () => {
31
+ const text = resolveLayoutFieldText(
32
+ { type: 'field', source: 'amount', format: 'currency-nonzero' },
33
+ { amount: '0.00' },
34
+ getProperty,
35
+ baseOptions,
36
+ )
37
+ expect(text).toBe('')
38
+ })
39
+
40
+ it('formats non-zero currency-nonzero fields as currency', () => {
41
+ const text = resolveLayoutFieldText(
42
+ { type: 'field', source: 'amount', format: 'currency-nonzero' },
43
+ { amount: '12.5' },
44
+ getProperty,
45
+ baseOptions,
46
+ )
47
+ expect(text).toBe('CUR:12.5')
48
+ })
49
+
50
+ it('suppresses number-nonzero fields with zero values', () => {
51
+ const text = resolveLayoutFieldText(
52
+ { type: 'field', source: 'qty', format: 'number-nonzero' },
53
+ { qty: 0 },
54
+ getProperty,
55
+ baseOptions,
56
+ )
57
+ expect(text).toBe('')
58
+ })
59
+
60
+ it('keeps regular number fields at zero', () => {
61
+ const text = resolveLayoutFieldText(
62
+ { type: 'field', source: 'qty', format: 'number' },
63
+ { qty: 0 },
64
+ getProperty,
65
+ baseOptions,
66
+ )
67
+ expect(text).toBe('0')
68
+ })
69
+ })
@@ -389,4 +389,58 @@ describe('genPDF', () => {
389
389
  // jsPDF encodes U+20AC as WinAnsi 0x80 in the text operator; still one glyph before digits.
390
390
  expect(pdfText).toMatch(/\(\s*.\s*10\.00\) Tj/)
391
391
  })
392
+
393
+ it('suppresses currency-nonzero fields when the value is zero', async () => {
394
+ const layout: Layout = {
395
+ name: 'currency-nonzero',
396
+ paperSize: { width: 210, height: 297, footerHeight: 20 },
397
+ objects: [
398
+ {
399
+ type: 'field' as const,
400
+ name: 'discount',
401
+ x: 20,
402
+ y: 20,
403
+ width: 500,
404
+ height: 50,
405
+ active: true,
406
+ source: 'discount',
407
+ format: 'currency-nonzero' as const,
408
+ textAlign: 1 as const,
409
+ fontFamily: 'Helvetica',
410
+ fontSize: 32,
411
+ fontStyle: 0,
412
+ },
413
+ ],
414
+ }
415
+ const doc = await genPDF(layout, { discount: '0.00' })
416
+ const pdfText = Buffer.from(doc.output('arraybuffer')).toString('latin1')
417
+ expect(pdfText).not.toMatch(/0\.00/)
418
+ })
419
+
420
+ it('renders currency-nonzero fields when the value is non-zero', async () => {
421
+ const layout: Layout = {
422
+ name: 'currency-nonzero-value',
423
+ paperSize: { width: 210, height: 297, footerHeight: 20 },
424
+ objects: [
425
+ {
426
+ type: 'field' as const,
427
+ name: 'discount',
428
+ x: 20,
429
+ y: 20,
430
+ width: 500,
431
+ height: 50,
432
+ active: true,
433
+ source: 'discount',
434
+ format: 'currency-nonzero' as const,
435
+ textAlign: 1 as const,
436
+ fontFamily: 'Helvetica',
437
+ fontSize: 32,
438
+ fontStyle: 0,
439
+ },
440
+ ],
441
+ }
442
+ const doc = await genPDF(layout, { discount: '5' })
443
+ const pdfText = Buffer.from(doc.output('arraybuffer')).toString('latin1')
444
+ expect(pdfText).toMatch(/\(\s*.\s*5\.00\) Tj/)
445
+ })
392
446
  })