@softwear/latestcollectioncore 1.0.187 → 1.0.189

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
@@ -14,6 +14,7 @@ export { default as hasOnlyDigits } from './hasOnlyDigits';
14
14
  export { default as imageBinder } from './imageBinder';
15
15
  export { default as isean13 } from './isean13';
16
16
  export { default as pivotTable } from './pivotTable';
17
+ export { default as projectObject } from './projectObject';
17
18
  export { default as reports, genPDF } from './reports';
18
19
  export { default as round2 } from './round2';
19
20
  export { default as sizeToMap } from './sizeToMap';
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.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.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");
@@ -50,6 +50,8 @@ var isean13_1 = require("./isean13");
50
50
  Object.defineProperty(exports, "isean13", { enumerable: true, get: function () { return __importDefault(isean13_1).default; } });
51
51
  var pivotTable_1 = require("./pivotTable");
52
52
  Object.defineProperty(exports, "pivotTable", { enumerable: true, get: function () { return __importDefault(pivotTable_1).default; } });
53
+ var projectObject_1 = require("./projectObject");
54
+ Object.defineProperty(exports, "projectObject", { enumerable: true, get: function () { return __importDefault(projectObject_1).default; } });
53
55
  var reports_1 = require("./reports");
54
56
  Object.defineProperty(exports, "reports", { enumerable: true, get: function () { return __importDefault(reports_1).default; } });
55
57
  Object.defineProperty(exports, "genPDF", { enumerable: true, get: function () { return reports_1.genPDF; } });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Whitelist projection: builds a new object from a deep-cloned copy of `source`,
3
+ * copying only the listed property names (nested values are cloned with `source`).
4
+ */
5
+ export default function projectObject<K extends string>(source: unknown, keys: readonly K[]): Record<K, unknown>;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const deepCopy_1 = __importDefault(require("./deepCopy"));
7
+ /**
8
+ * Whitelist projection: builds a new object from a deep-cloned copy of `source`,
9
+ * copying only the listed property names (nested values are cloned with `source`).
10
+ */
11
+ function projectObject(source, keys) {
12
+ const out = {};
13
+ if (source == null)
14
+ return out;
15
+ if (typeof source !== 'object')
16
+ return out;
17
+ const tmp = (0, deepCopy_1.default)(source);
18
+ for (const k of keys) {
19
+ out[k] = tmp[k];
20
+ }
21
+ return out;
22
+ }
23
+ exports.default = projectObject;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwear/latestcollectioncore",
3
- "version": "1.0.187",
3
+ "version": "1.0.189",
4
4
  "description": "Core functions for LatestCollections applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export { default as hasOnlyDigits } from './hasOnlyDigits'
14
14
  export { default as imageBinder } from './imageBinder'
15
15
  export { default as isean13 } from './isean13'
16
16
  export { default as pivotTable } from './pivotTable'
17
+ export { default as projectObject } from './projectObject'
17
18
  export { default as reports, genPDF } from './reports'
18
19
  export { default as round2 } from './round2'
19
20
  export { default as sizeToMap } from './sizeToMap'
@@ -0,0 +1,16 @@
1
+ import deepCopy from './deepCopy'
2
+
3
+ /**
4
+ * Whitelist projection: builds a new object from a deep-cloned copy of `source`,
5
+ * copying only the listed property names (nested values are cloned with `source`).
6
+ */
7
+ export default function projectObject<K extends string>(source: unknown, keys: readonly K[]): Record<K, unknown> {
8
+ const out = {} as Record<K, unknown>
9
+ if (source == null) return out
10
+ if (typeof source !== 'object') return out
11
+ const tmp = deepCopy(source) as Record<string, unknown>
12
+ for (const k of keys) {
13
+ out[k] = tmp[k]
14
+ }
15
+ return out
16
+ }
package/src/reports.ts CHANGED
@@ -32,6 +32,12 @@ export interface PdfImageAsset {
32
32
  export interface GenPdfOptions {
33
33
  onAlert?: (alert: GenPdfAlert) => void
34
34
  translate?: (key: string) => string
35
+ /**
36
+ * Called for layout fields with `format: 'currency'`. Receives the raw string from `source` (e.g. `"123.45"`).
37
+ * Return the exact string to draw. Callers normally pass a closure created next to `genPDF` so it can read
38
+ * `printBuffer`, tenant currency, locale, etc. If omitted, defaults to a euro prefix plus two decimals (`€ 123.45`).
39
+ */
40
+ formatCurrency?: (rawAmountString: string) => string
35
41
  resolveLogoUrl?: () => string | undefined
36
42
  isCustomPdfFont?: (fontFamily: string) => boolean
37
43
  getPdfFontDefinition?: (fontFamily: string) => PdfFontDefinition | undefined
@@ -353,7 +359,10 @@ function drawSimpleObject(x: number, y: number, doc: jsPDF, object: LayoutObject
353
359
  options.onAlert?.({ header: 'fieldnotfound', body: object.source, type: 'warning', timeout: 10000 })
354
360
  text = ''
355
361
  }
356
- if (object.format == 'currency') text = String.fromCharCode(128) + ' ' + parseFloat(text).toFixed(2)
362
+ if (object.format == 'currency') {
363
+ const raw = text
364
+ text = options.formatCurrency ? options.formatCurrency(raw) : '€ ' + parseFloat(raw).toFixed(2)
365
+ }
357
366
  }
358
367
  // Manipulate case as specified in object
359
368
  if ((object.type === 'text' || object.type === 'field') && object.case === 1) text = text.toUpperCase()
@@ -586,12 +595,7 @@ addObjectToPDF = function (
586
595
  const layoutYtenths = originY + object.y
587
596
  const rawPrintOnlyAtStartOffset = printOnlyAtStartContainersOffset ?? 0
588
597
  /** Must consult current page on every read: `addPage` can move to page 2+ mid-invocation while this call stays open. */
589
- const effectivePrintOnlyAtStartContainersOffsetNow = (): number =>
590
- containerChain.length === 0 &&
591
- rawPrintOnlyAtStartOffset > 0 &&
592
- getCurrentPageNumber(doc, context) === 1
593
- ? rawPrintOnlyAtStartOffset
594
- : 0
598
+ const effectivePrintOnlyAtStartContainersOffsetNow = (): number => (containerChain.length === 0 && rawPrintOnlyAtStartOffset > 0 && getCurrentPageNumber(doc, context) === 1 ? rawPrintOnlyAtStartOffset : 0)
595
599
 
596
600
  const x = layoutXtenths / 10
597
601
  const y = (layoutYtenths + effectivePrintOnlyAtStartContainersOffsetNow()) / 10
@@ -0,0 +1,25 @@
1
+ const { deepCopy, projectObject } = require('../dist/index')
2
+
3
+ describe('projectObject', function () {
4
+ it('returns an empty object for null/undefined', function () {
5
+ expect(projectObject(null, ['a'])).toEqual({})
6
+ expect(projectObject(undefined, ['a'])).toEqual({})
7
+ })
8
+ it('returns an empty object for non-object primitives', function () {
9
+ expect(projectObject('x', ['a'])).toEqual({})
10
+ expect(projectObject(1, ['a'])).toEqual({})
11
+ })
12
+ it('copies only listed keys from a deep-cloned source', function () {
13
+ const src = { id: '1', name: 'n', extra: 'drop', nest: { x: 1 } }
14
+ const p = projectObject(src, ['id', 'nest'])
15
+ expect(p).toEqual({ id: '1', nest: { x: 1 } })
16
+ expect(p.nest).not.toBe(src.nest)
17
+ expect(deepCopy(src.nest)).toEqual(p.nest)
18
+ })
19
+ it('does not mutate the original when copying nested data', function () {
20
+ const src = { a: { b: 1 } }
21
+ const p = projectObject(src, ['a'])
22
+ p.a.b = 2
23
+ expect(src.a.b).toBe(1)
24
+ })
25
+ })
@@ -331,4 +331,62 @@ describe('genPDF', () => {
331
331
  expect(chromeMatches?.length ?? 0).toBeGreaterThanOrEqual(doc.getNumberOfPages())
332
332
  expect(pdfText).toContain('SKU_20')
333
333
  })
334
+
335
+ it('uses GenPdfOptions.formatCurrency for currency fields when provided', async () => {
336
+ const layout: Layout = {
337
+ name: 'currency-inject',
338
+ paperSize: { width: 210, height: 297, footerHeight: 20 },
339
+ objects: [
340
+ {
341
+ type: 'field' as const,
342
+ name: 'amount',
343
+ x: 20,
344
+ y: 20,
345
+ width: 500,
346
+ height: 50,
347
+ active: true,
348
+ source: 'amount',
349
+ format: 'currency' as const,
350
+ textAlign: 1 as const,
351
+ fontFamily: 'Helvetica',
352
+ fontSize: 32,
353
+ fontStyle: 0,
354
+ },
355
+ ],
356
+ }
357
+ const printBuffer = { amount: '99.5', currency: 'USD' }
358
+ const doc = await genPDF(layout, printBuffer, {
359
+ formatCurrency: (raw) => `USD ${raw} BUF:${printBuffer.currency}`,
360
+ })
361
+ const pdfText = Buffer.from(doc.output('arraybuffer')).toString('latin1')
362
+ expect(pdfText).toContain('USD 99.5 BUF:USD')
363
+ })
364
+
365
+ it('defaults currency fields to euro-style prefix when formatCurrency is omitted', async () => {
366
+ const layout: Layout = {
367
+ name: 'currency-default',
368
+ paperSize: { width: 210, height: 297, footerHeight: 20 },
369
+ objects: [
370
+ {
371
+ type: 'field' as const,
372
+ name: 'amount',
373
+ x: 20,
374
+ y: 20,
375
+ width: 500,
376
+ height: 50,
377
+ active: true,
378
+ source: 'amount',
379
+ format: 'currency' as const,
380
+ textAlign: 1 as const,
381
+ fontFamily: 'Helvetica',
382
+ fontSize: 32,
383
+ fontStyle: 0,
384
+ },
385
+ ],
386
+ }
387
+ const doc = await genPDF(layout, { amount: '10' })
388
+ const pdfText = Buffer.from(doc.output('arraybuffer')).toString('latin1')
389
+ // jsPDF encodes U+20AC as WinAnsi 0x80 in the text operator; still one glyph before digits.
390
+ expect(pdfText).toMatch(/\(\s*.\s*10\.00\) Tj/)
391
+ })
334
392
  })