@softwear/latestcollectioncore 1.0.163 → 1.0.165

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.
@@ -90,6 +90,7 @@ declare const _default: {
90
90
  QTY_B2B_RETURN: number;
91
91
  RETURN_PERCENTAGE: number;
92
92
  ROI: number;
93
+ ROI_PERCENTAGE: number;
93
94
  SELLOUT_PERCENTAGE: number;
94
95
  STOCKAMOUNT_TIME_PRODUCT: number;
95
96
  STOCK_TIME_PRODUCT: number;
@@ -13,7 +13,7 @@ const { QTY_TRANSACTION, QTY_STOCK, AMOUNT_STOCK, QTY_SHELF_STOCK, AMOUNT_SHELF_
13
13
  // Derived fields
14
14
  AMOUNT_SOLD_MAX, AMOUNT_SOLD_DISCOUNT, MAX_RECIEVE_TIMESTAMP, STOCK_TIME_PRODUCT, STOCKAMOUNT_TIME_PRODUCT, QTY_RETURN, AMOUNT_RETURN,
15
15
  // Post aggregation fields
16
- SELLOUT_PERCENTAGE, ROI, QTY_AVG_STOCK, VALUE_AVG_STOCK, QTY_TURNOVER_VELOCITY, AMOUNT_TURNOVER_VELOCITY, PROFITABILITY, MARGIN, PROFIT, QTY_END_SHELF_STOCK, AMOUNT_END_SHELF_STOCK, QTY_END_STOCK, AMOUNT_END_STOCK, RETURN_PERCENTAGE, QTY_SOLD_BEFORE_RETURNS, } = transaction_1.default.dbTransactionVector;
16
+ SELLOUT_PERCENTAGE, ROI, QTY_AVG_STOCK, VALUE_AVG_STOCK, QTY_TURNOVER_VELOCITY, AMOUNT_TURNOVER_VELOCITY, PROFITABILITY, MARGIN, PROFIT, QTY_END_SHELF_STOCK, AMOUNT_END_SHELF_STOCK, QTY_END_STOCK, AMOUNT_END_STOCK, RETURN_PERCENTAGE, QTY_SOLD_BEFORE_RETURNS, ROI_PERCENTAGE, } = transaction_1.default.dbTransactionVector;
17
17
  // Vector length to allocate for aggregation
18
18
  const VECTOR_LENGTH = 50;
19
19
  const BEGIN_STOCK_VECTOR_START = QTY_STOCK;
@@ -290,6 +290,8 @@ function postAgg(v, timeFrame) {
290
290
  v[MARGIN] = (v[PROFIT] / totalAmountSold) * 100;
291
291
  v[RETURN_PERCENTAGE] = (v[QTY_RETURN] / v[QTY_RECIEVED]) * 100;
292
292
  v[QTY_SOLD_BEFORE_RETURNS] = totalQtySold + Math.abs(v[QTY_RETURN]);
293
+ const investmentBase = v[AMOUNT_RECIEVED] - v[AMOUNT_TRANSIT];
294
+ v[ROI_PERCENTAGE] = investmentBase <= 0 ? NaN : ((v[ROI] + investmentBase) / investmentBase) * 100;
293
295
  v[QTY_SOLD] = totalQtySold;
294
296
  v[AMOUNT_SOLD_EXCL] = totalAmountSold;
295
297
  v[COSTPRICE_SOLD] = totalCostSold;
@@ -522,6 +524,7 @@ exports.default = {
522
524
  QTY_B2B_RETURN,
523
525
  RETURN_PERCENTAGE,
524
526
  ROI,
527
+ ROI_PERCENTAGE,
525
528
  SELLOUT_PERCENTAGE,
526
529
  STOCKAMOUNT_TIME_PRODUCT,
527
530
  STOCK_TIME_PRODUCT,
@@ -0,0 +1,7 @@
1
+ import type { SkuI } from './types';
2
+ /**
3
+ * Fills missing SKU fields from redundant sources (supplier mirrors, articleCode, id, etc.).
4
+ * Then sets UI-only `PRODUCT` to the clustering key (phase 1: same as `articleCodeSupplier` after normalization).
5
+ * Mutates the sku in place.
6
+ */
7
+ export default function ensureImpliedProperties(sku: SkuI): void;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * Fills missing SKU fields from redundant sources (supplier mirrors, articleCode, id, etc.).
5
+ * Then sets UI-only `PRODUCT` to the clustering key (phase 1: same as `articleCodeSupplier` after normalization).
6
+ * Mutates the sku in place.
7
+ */
8
+ function ensureImpliedProperties(sku) {
9
+ var _a;
10
+ if (!sku.articleGroupSupplier)
11
+ sku.articleGroupSupplier = sku.articleGroup;
12
+ if (!sku.collectionSupplier)
13
+ sku.collectionSupplier = sku.collection;
14
+ if (!sku.colorCodeSupplier)
15
+ sku.colorCodeSupplier = sku.colorCode;
16
+ if (!sku.sizeSupplier)
17
+ sku.sizeSupplier = sku.size;
18
+ if (!sku.brandSupplier)
19
+ sku.brandSupplier = sku.brand;
20
+ if (!sku.colorFamily && sku.colorSupplier)
21
+ sku.colorFamily = '' + sku.colorSupplier;
22
+ if (!sku.colorFamily && sku.colorDescription)
23
+ sku.colorFamily = '' + sku.colorDescription;
24
+ if (!sku.colorFamily && sku.mainColorDescription)
25
+ sku.colorFamily = '' + sku.mainColorDescription;
26
+ if (!sku.articleCodeSupplier)
27
+ sku.articleCodeSupplier = sku.articleCode;
28
+ if (!sku.size && sku.sizeSupplier)
29
+ sku.size = sku.sizeSupplier;
30
+ if (!sku.barcode)
31
+ sku.barcode = sku.id;
32
+ sku.PRODUCT = (_a = sku.articleCodeSupplier) !== null && _a !== void 0 ? _a : '';
33
+ }
34
+ exports.default = ensureImpliedProperties;
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export { default as deepCopy } from './deepCopy';
4
4
  export { default as ean13 } from './ean13';
5
5
  export { default as edifact } from './edifact';
6
6
  export { default as ensureArray } from './ensureArray';
7
+ export { default as ensureImpliedProperties } from './ensureImpliedProperties';
7
8
  export { default as findSkuByBarcode } from './findSkuByBarcode';
8
9
  export { default as getBrandName } from './getBrandName';
9
10
  export { default as getPreferedPropertyMappings } from './getPreferedPropertyMappings';
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.pivotTable = exports.isean13 = exports.imageBinder = exports.hasOnlyDigits = exports.hashBrand = exports.getPreferedPropertyMappings = exports.getBrandName = exports.findSkuByBarcode = 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.pivotTable = exports.isean13 = exports.imageBinder = exports.hasOnlyDigits = exports.hashBrand = exports.getPreferedPropertyMappings = exports.getBrandName = 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");
@@ -30,6 +30,8 @@ var edifact_1 = require("./edifact");
30
30
  Object.defineProperty(exports, "edifact", { enumerable: true, get: function () { return __importDefault(edifact_1).default; } });
31
31
  var ensureArray_1 = require("./ensureArray");
32
32
  Object.defineProperty(exports, "ensureArray", { enumerable: true, get: function () { return __importDefault(ensureArray_1).default; } });
33
+ var ensureImpliedProperties_1 = require("./ensureImpliedProperties");
34
+ Object.defineProperty(exports, "ensureImpliedProperties", { enumerable: true, get: function () { return __importDefault(ensureImpliedProperties_1).default; } });
33
35
  var findSkuByBarcode_1 = require("./findSkuByBarcode");
34
36
  Object.defineProperty(exports, "findSkuByBarcode", { enumerable: true, get: function () { return __importDefault(findSkuByBarcode_1).default; } });
35
37
  var getBrandName_1 = require("./getBrandName");
@@ -66,6 +66,7 @@ declare const _default: {
66
66
  AMOUNT_END_STOCK: number;
67
67
  RETURN_PERCENTAGE: number;
68
68
  QTY_SOLD_BEFORE_RETURNS: number;
69
+ ROI_PERCENTAGE: number;
69
70
  };
70
71
  buildTransaction: (transaction: TransactionI) => dbTransactionI | undefined;
71
72
  buildVector: (transaction: TransactionI) => Float64Array;
@@ -71,6 +71,7 @@ const dbTransactionVector = {
71
71
  AMOUNT_END_STOCK: 62,
72
72
  RETURN_PERCENTAGE: 63,
73
73
  QTY_SOLD_BEFORE_RETURNS: 64,
74
+ ROI_PERCENTAGE: 65,
74
75
  };
75
76
  const tv = dbTransactionVector;
76
77
  const buildVector = function (transaction) {
package/dist/types.d.ts CHANGED
@@ -131,6 +131,8 @@ interface SkuI {
131
131
  IMAGES?: Array<ImageI>;
132
132
  SIZES?: unknown;
133
133
  FOOTPRINT?: number;
134
+ /** UI-only clustering key for product tiles / matrices; not persisted. Phase 1 mirrors articleCodeSupplier after ensureImpliedProperties. */
135
+ PRODUCT?: string;
134
136
  quickPick?: number;
135
137
  gln?: string;
136
138
  set?: string;
@@ -225,9 +227,6 @@ interface BrandSettingI {
225
227
  gln?: string;
226
228
  ownerTenantId?: string;
227
229
  orderMethods?: string[];
228
- whiteListTenants?: string[];
229
- associates?: object[];
230
- authGroups?: string[];
231
230
  propertyMapping?: PropertyMappingI[];
232
231
  fashionCloudId?: string;
233
232
  sizeSystemPrefix?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwear/latestcollectioncore",
3
- "version": "1.0.163",
3
+ "version": "1.0.165",
4
4
  "description": "Core functions for LatestCollections applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,8 +18,7 @@
18
18
  "build": "tsc -p tsconfig.json",
19
19
  "build:watch": "tsc -p tsconfig.json --watch",
20
20
  "test": "vitest run",
21
- "test:watch": "vitest",
22
- "dev": "concurrently \"npm:build:watch\" \"npm:test:watch\""
21
+ "test:watch": "vitest"
23
22
  },
24
23
  "repository": {
25
24
  "type": "git",
@@ -33,7 +32,6 @@
33
32
  "homepage": "https://gitlab.com/softwearconnect/latestcollectioncore#readme",
34
33
  "devDependencies": {
35
34
  "@types/node": "^18.11.17",
36
- "concurrently": "^9.2.1",
37
35
  "typescript": "^4.9.4",
38
36
  "vitest": "^1.0.0"
39
37
  },
@@ -75,6 +75,7 @@ const {
75
75
  AMOUNT_END_STOCK,
76
76
  RETURN_PERCENTAGE,
77
77
  QTY_SOLD_BEFORE_RETURNS,
78
+ ROI_PERCENTAGE,
78
79
  } = transactionFunctions.dbTransactionVector
79
80
 
80
81
  // Vector length to allocate for aggregation
@@ -375,6 +376,8 @@ function postAgg(v: any, timeFrame: number): void {
375
376
  v[MARGIN] = (v[PROFIT] / totalAmountSold) * 100
376
377
  v[RETURN_PERCENTAGE] = (v[QTY_RETURN] / v[QTY_RECIEVED]) * 100
377
378
  v[QTY_SOLD_BEFORE_RETURNS] = totalQtySold + Math.abs(v[QTY_RETURN])
379
+ const investmentBase = v[AMOUNT_RECIEVED] - v[AMOUNT_TRANSIT]
380
+ v[ROI_PERCENTAGE] = investmentBase <= 0 ? NaN : ((v[ROI] + investmentBase) / investmentBase) * 100
378
381
  v[QTY_SOLD] = totalQtySold
379
382
  v[AMOUNT_SOLD_EXCL] = totalAmountSold
380
383
  v[COSTPRICE_SOLD] = totalCostSold
@@ -616,6 +619,7 @@ export default {
616
619
  QTY_B2B_RETURN,
617
620
  RETURN_PERCENTAGE,
618
621
  ROI,
622
+ ROI_PERCENTAGE,
619
623
  SELLOUT_PERCENTAGE,
620
624
  STOCKAMOUNT_TIME_PRODUCT,
621
625
  STOCK_TIME_PRODUCT,
@@ -0,0 +1,23 @@
1
+ import type { SkuI } from './types'
2
+
3
+ /**
4
+ * Fills missing SKU fields from redundant sources (supplier mirrors, articleCode, id, etc.).
5
+ * Then sets UI-only `PRODUCT` to the clustering key (phase 1: same as `articleCodeSupplier` after normalization).
6
+ * Mutates the sku in place.
7
+ */
8
+ export default function ensureImpliedProperties(sku: SkuI): void {
9
+ if (!sku.articleGroupSupplier) sku.articleGroupSupplier = sku.articleGroup
10
+ if (!sku.collectionSupplier) sku.collectionSupplier = sku.collection
11
+ if (!sku.colorCodeSupplier) sku.colorCodeSupplier = sku.colorCode
12
+ if (!sku.sizeSupplier) sku.sizeSupplier = sku.size
13
+ if (!sku.brandSupplier) sku.brandSupplier = sku.brand
14
+
15
+ if (!sku.colorFamily && sku.colorSupplier) sku.colorFamily = '' + sku.colorSupplier
16
+ if (!sku.colorFamily && sku.colorDescription) sku.colorFamily = '' + sku.colorDescription
17
+ if (!sku.colorFamily && sku.mainColorDescription) sku.colorFamily = '' + sku.mainColorDescription
18
+ if (!sku.articleCodeSupplier) sku.articleCodeSupplier = sku.articleCode
19
+ if (!sku.size && sku.sizeSupplier) sku.size = sku.sizeSupplier
20
+ if (!sku.barcode) sku.barcode = sku.id
21
+
22
+ sku.PRODUCT = sku.articleCodeSupplier ?? ''
23
+ }
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ export { default as deepCopy } from './deepCopy'
4
4
  export { default as ean13 } from './ean13'
5
5
  export { default as edifact } from './edifact'
6
6
  export { default as ensureArray } from './ensureArray'
7
+ export { default as ensureImpliedProperties } from './ensureImpliedProperties'
7
8
  export { default as findSkuByBarcode } from './findSkuByBarcode'
8
9
  export { default as getBrandName } from './getBrandName'
9
10
  export { default as getPreferedPropertyMappings } from './getPreferedPropertyMappings'
@@ -73,6 +73,7 @@ const dbTransactionVector = {
73
73
  AMOUNT_END_STOCK: 62,
74
74
  RETURN_PERCENTAGE: 63,
75
75
  QTY_SOLD_BEFORE_RETURNS: 64,
76
+ ROI_PERCENTAGE: 65,
76
77
  }
77
78
  const tv = dbTransactionVector
78
79
 
package/src/types.ts CHANGED
@@ -140,6 +140,8 @@ interface SkuI {
140
140
  IMAGES?: Array<ImageI>
141
141
  SIZES?: unknown
142
142
  FOOTPRINT?: number
143
+ /** UI-only clustering key for product tiles / matrices; not persisted. Phase 1 mirrors articleCodeSupplier after ensureImpliedProperties. */
144
+ PRODUCT?: string
143
145
  quickPick?: number
144
146
  gln?: string
145
147
  set?: string
@@ -239,9 +241,6 @@ interface BrandSettingI {
239
241
  gln?: string
240
242
  ownerTenantId?: string
241
243
  orderMethods?: string[]
242
- whiteListTenants?: string[]
243
- associates?: object[]
244
- authGroups?: string[]
245
244
  propertyMapping?: PropertyMappingI[]
246
245
  fashionCloudId?: string
247
246
  sizeSystemPrefix?: string
@@ -123,6 +123,9 @@ describe('articleStatus', () => {
123
123
  it('QTY_SOLD_BEFORE_RETURNS should be 64', () => {
124
124
  expect(articleStatus.QTY_SOLD_BEFORE_RETURNS).toBe(64)
125
125
  })
126
+ it('ROI_PERCENTAGE should be 65', () => {
127
+ expect(articleStatus.ROI_PERCENTAGE).toBe(65)
128
+ })
126
129
  it('AMOUNT_RETURN should be 49', () => {
127
130
  expect(articleStatus.AMOUNT_RETURN).toBe(49)
128
131
  })
@@ -414,5 +417,27 @@ describe('articleStatus', () => {
414
417
  expect(dateRange.hrDay).toBe('2024-01-01')
415
418
  expect(dateRange.timeFrame).toBe(367 * 24 * 3600 * 1000)
416
419
  })
420
+
421
+ it('should calculate ROI percentage from post-aggregated values', () => {
422
+ const vector = new Array(articleStatus.ROI_PERCENTAGE + 1).fill(0)
423
+ vector[articleStatus.AMOUNT_RECIEVED] = 8700
424
+ vector[articleStatus.AMOUNT_SOLD_EXCL] = 5900
425
+
426
+ articleStatus.postAgg(vector, msYear)
427
+
428
+ expect(vector[articleStatus.ROI]).toBe(-2800)
429
+ expect(vector[articleStatus.ROI_PERCENTAGE]).toBeCloseTo(67.81609195402298, 10)
430
+ })
431
+
432
+ it('should set ROI percentage to NaN when investment base is zero or negative', () => {
433
+ const vector = new Array(articleStatus.ROI_PERCENTAGE + 1).fill(0)
434
+ vector[articleStatus.AMOUNT_RECIEVED] = 100
435
+ vector[articleStatus.AMOUNT_TRANSIT] = 100
436
+ vector[articleStatus.AMOUNT_SOLD_EXCL] = 150
437
+
438
+ articleStatus.postAgg(vector, msYear)
439
+
440
+ expect(Number.isNaN(vector[articleStatus.ROI_PERCENTAGE])).toBe(true)
441
+ })
417
442
  })
418
443
  })
@@ -0,0 +1,95 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import ensureImpliedProperties from '../src/ensureImpliedProperties'
3
+ import type { SkuI } from '../src/types'
4
+
5
+ function baseSku(overrides: Partial<SkuI> = {}): SkuI {
6
+ return {
7
+ articleCode: 'AC-1',
8
+ articleDescription: 'Desc',
9
+ brand: 'BrandX',
10
+ colorFamily: 'Black',
11
+ size: 'M',
12
+ ...overrides,
13
+ } as SkuI
14
+ }
15
+
16
+ describe('ensureImpliedProperties', () => {
17
+ it('fills articleCodeSupplier from articleCode and sets PRODUCT to match', () => {
18
+ const sku = baseSku({ articleCode: 'SUP-99' })
19
+ delete (sku as { articleCodeSupplier?: string }).articleCodeSupplier
20
+
21
+ ensureImpliedProperties(sku)
22
+
23
+ expect(sku.articleCodeSupplier).toBe('SUP-99')
24
+ expect(sku.PRODUCT).toBe('SUP-99')
25
+ })
26
+
27
+ it('sets PRODUCT from existing articleCodeSupplier without overwriting', () => {
28
+ const sku = baseSku({
29
+ articleCode: 'AC-1',
30
+ articleCodeSupplier: 'ALREADY-SET',
31
+ })
32
+
33
+ ensureImpliedProperties(sku)
34
+
35
+ expect(sku.articleCodeSupplier).toBe('ALREADY-SET')
36
+ expect(sku.PRODUCT).toBe('ALREADY-SET')
37
+ })
38
+
39
+ it('sets barcode from id when barcode is missing', () => {
40
+ const sku = baseSku({
41
+ id: 'ID-12345',
42
+ barcode: undefined,
43
+ })
44
+
45
+ ensureImpliedProperties(sku)
46
+
47
+ expect(sku.barcode).toBe('ID-12345')
48
+ expect(sku.PRODUCT).toBe('AC-1')
49
+ })
50
+
51
+ it('fills colorFamily from colorSupplier when colorFamily is missing', () => {
52
+ const sku = baseSku({
53
+ colorFamily: undefined as unknown as string,
54
+ colorSupplier: 'Navy',
55
+ })
56
+
57
+ ensureImpliedProperties(sku)
58
+
59
+ expect(sku.colorFamily).toBe('Navy')
60
+ expect(sku.PRODUCT).toBe('AC-1')
61
+ })
62
+
63
+ it('fills supplier mirror fields from primary fields', () => {
64
+ const sku = baseSku({
65
+ articleGroup: 'G1',
66
+ collection: 'C1',
67
+ colorCode: 'CC',
68
+ brand: 'B1',
69
+ })
70
+ delete (sku as { articleGroupSupplier?: string }).articleGroupSupplier
71
+ delete (sku as { collectionSupplier?: string }).collectionSupplier
72
+ delete (sku as { colorCodeSupplier?: string }).colorCodeSupplier
73
+ delete (sku as { brandSupplier?: string }).brandSupplier
74
+
75
+ ensureImpliedProperties(sku)
76
+
77
+ expect(sku.articleGroupSupplier).toBe('G1')
78
+ expect(sku.collectionSupplier).toBe('C1')
79
+ expect(sku.colorCodeSupplier).toBe('CC')
80
+ expect(sku.brandSupplier).toBe('B1')
81
+ expect(sku.PRODUCT).toBe('AC-1')
82
+ })
83
+
84
+ it('sets PRODUCT to empty string when articleCodeSupplier is still missing after fallbacks', () => {
85
+ const sku = baseSku({
86
+ articleCode: '',
87
+ articleCodeSupplier: undefined,
88
+ })
89
+
90
+ ensureImpliedProperties(sku)
91
+
92
+ expect(sku.articleCodeSupplier).toBe('')
93
+ expect(sku.PRODUCT).toBe('')
94
+ })
95
+ })