@softwear/latestcollectioncore 1.0.202 → 1.0.204

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.
@@ -33,7 +33,7 @@ function resolvePath(obj, path) {
33
33
  return path.reduce((o, key) => o === null || o === void 0 ? void 0 : o[key], obj);
34
34
  }
35
35
  function propFunction(propertyName, getPropertyName, obj) {
36
- var _a, _b;
36
+ var _a;
37
37
  const positionOfDot = propertyName.indexOf('.');
38
38
  if (positionOfDot < 0)
39
39
  return obj[propertyName];
@@ -42,7 +42,7 @@ function propFunction(propertyName, getPropertyName, obj) {
42
42
  if (table == 'transaction')
43
43
  return resolvePath(obj, path);
44
44
  const joinedRecord = (_a = globalRelations[table]) === null || _a === void 0 ? void 0 : _a[obj[globalJoinableTables[table]]];
45
- return (_b = resolvePath(joinedRecord, path)) !== null && _b !== void 0 ? _b : '~ ?';
45
+ return resolvePath(joinedRecord, path) || '~ ?';
46
46
  }
47
47
  const lower = (s) => s.toLowerCase();
48
48
  const upper = (s) => s.toUpperCase();
@@ -86,18 +86,9 @@ function addTransactionToAggregations(beginTimestamp, endTimestamp, aggregateKey
86
86
  // Calculate derived fields
87
87
  aggregateVector[QTY_RETURN] += v[QTY_SOLD] < 0 ? v[QTY_SOLD] : 0;
88
88
  aggregateVector[AMOUNT_RETURN] += v[AMOUNT_SOLD_EXCL] < 0 ? v[AMOUNT_SOLD_EXCL] : 0;
89
- aggregateVector[STOCK_TIME_PRODUCT] +=
90
- ((v[QTY_RECIEVED] || 0) + (v[QTY_TRANSIT] || 0) + (v[QTY_CHANGE] || 0) - (v[QTY_SOLD] || 0) - (v[QTY_B2B_SOLD] || 0) - (v[QTY_B2B_RESOLD] || 0) + (v[QTY_B2B_RETURN] || 0)) *
91
- (endTimestamp - transaction.time);
89
+ aggregateVector[STOCK_TIME_PRODUCT] += ((v[QTY_RECIEVED] || 0) + (v[QTY_TRANSIT] || 0) + (v[QTY_CHANGE] || 0) - (v[QTY_SOLD] || 0) - (v[QTY_B2B_SOLD] || 0) - (v[QTY_B2B_RESOLD] || 0) + (v[QTY_B2B_RETURN] || 0)) * (endTimestamp - transaction.time);
92
90
  aggregateVector[STOCKAMOUNT_TIME_PRODUCT] +=
93
- ((v[AMOUNT_RECIEVED] || 0) +
94
- (v[AMOUNT_TRANSIT] || 0) +
95
- (v[AMOUNT_CHANGE] || 0) -
96
- (v[COSTPRICE_SOLD] || 0) -
97
- (v[COSTPRICE_B2B_SOLD] || 0) -
98
- (v[COSTPRICE_B2B_RESOLD] || 0) +
99
- (v[COSTPRICE_B2B_RETURN] || 0)) *
100
- (endTimestamp - transaction.time);
91
+ ((v[AMOUNT_RECIEVED] || 0) + (v[AMOUNT_TRANSIT] || 0) + (v[AMOUNT_CHANGE] || 0) - (v[COSTPRICE_SOLD] || 0) - (v[COSTPRICE_B2B_SOLD] || 0) - (v[COSTPRICE_B2B_RESOLD] || 0) + (v[COSTPRICE_B2B_RETURN] || 0)) * (endTimestamp - transaction.time);
101
92
  }
102
93
  function buildAggregationEvaluator(aggregationExpression) {
103
94
  return (0, filtrex_1.compileExpression)(aggregationExpression, filtrexOptions);
@@ -149,7 +140,7 @@ function getTrieLeaf(root, transaction, keys) {
149
140
  *
150
141
  * @returns {Function} - The aggregator function
151
142
  */
152
- function aggregator({ beginTimestamp, endTimestamp, filterExpression, aggregationExpression, rawAggregations, maxRows, totals, subtotalsForOuterGrouper, parentGroupTotals, }) {
143
+ function aggregator({ beginTimestamp, endTimestamp, filterExpression, aggregationExpression, rawAggregations, maxRows, totals, subtotalsForOuterGrouper, parentGroupTotals }) {
153
144
  let filter;
154
145
  let filterGranularity = [];
155
146
  const filterTrieRoot = {};
@@ -219,7 +210,7 @@ function aggregator({ beginTimestamp, endTimestamp, filterExpression, aggregatio
219
210
  }
220
211
  };
221
212
  }
222
- function aggregate({ beginTimestamp, endTimestamp, filterExpression, aggregationExpression, transactions, beginStock, relations, rawAggregations, maxRows, totals, subtotalsForOuterGrouper, parentGroupTotals, }) {
213
+ function aggregate({ beginTimestamp, endTimestamp, filterExpression, aggregationExpression, transactions, beginStock, relations, rawAggregations, maxRows, totals, subtotalsForOuterGrouper, parentGroupTotals }) {
223
214
  globalNrAggegationExpressions = 0;
224
215
  globalRelations = relations;
225
216
  const compiledAggregator = aggregator({
@@ -290,8 +281,7 @@ function postAgg(v, timeFrame) {
290
281
  // - beginStock from type 0 (same as QTY_STOCK)
291
282
  // - beginShelfStock adjustment from type 10 (negative picktickets from previous shards)
292
283
  v[QTY_END_SHELF_STOCK] = v[QTY_SHELF_STOCK] + v[QTY_RECIEVED] + v[QTY_CHANGE] + v[QTY_TRANSIT] - totalQtySold - v[QTY_CONSIGNMENT] - v[QTY_PICKLIST];
293
- v[AMOUNT_END_SHELF_STOCK] =
294
- v[AMOUNT_SHELF_STOCK] + v[AMOUNT_RECIEVED] + v[AMOUNT_CHANGE] + v[AMOUNT_TRANSIT] + v[AMOUNT_REVALUATE] - totalCostSold - v[AMOUNT_CONSIGNMENT] - v[AMOUNT_PICKLIST];
284
+ v[AMOUNT_END_SHELF_STOCK] = v[AMOUNT_SHELF_STOCK] + v[AMOUNT_RECIEVED] + v[AMOUNT_CHANGE] + v[AMOUNT_TRANSIT] + v[AMOUNT_REVALUATE] - totalCostSold - v[AMOUNT_CONSIGNMENT] - v[AMOUNT_PICKLIST];
295
285
  v[SELLOUT_PERCENTAGE] = (totalQtySold / (totalQtySold + v[QTY_END_STOCK])) * 100;
296
286
  v[ROI] = totalAmountSold - v[AMOUNT_RECIEVED] + v[AMOUNT_TRANSIT];
297
287
  v[QTY_AVG_STOCK] = (v[QTY_STOCK] * timeFrame + v[STOCK_TIME_PRODUCT]) / timeFrame;
@@ -6,7 +6,7 @@ declare function imageBinder(fileNames: Array<string>, filteredSkus: SkuI[], bra
6
6
  fallbackCount: number;
7
7
  patternCounter: {};
8
8
  };
9
- declare function testBatch(brand: string): Promise<{
9
+ declare function testBatch(brand: string, specifiedPattern?: Array<string>): Promise<{
10
10
  Brand: string;
11
11
  Files: any;
12
12
  Skus: any;
@@ -60,10 +60,82 @@ const GLOBAL_PATTERNS = [
60
60
  ['articleDescription', 'articleCode|articleCodeSupplier', 'colorCodeSupplier|colorSupplier'],
61
61
  ['colorCodeSupplier|colorSupplier', 'articleCode|articleCodeSupplier'],
62
62
  ['brand', 'collectionSupplier', 'colorCodeSupplier', 'articleCode|articleCodeSupplier'],
63
- ['brand', 'collectionSupplier', 'colorCodeSupplier', 'articleCode|articleCodeSupplier', 'buyPrice|valuePrice'],
64
63
  ['brand', 'articleDescription', 'colorSupplier'],
65
64
  ];
66
- function prepFilterSkusImages(filteredSkus, merge = true, fileNames) {
65
+ const FIELDS_TO_SKIP = ['__created', '__modified', 'images', 'brandhash', 'active', 'PRODUCT', 'colorFamily', 'price', 'sizeIndex', '_customsize', 'SELECTED'];
66
+ const MERGE_GROUPS = {
67
+ 'id|barcode': 'id',
68
+ 'articleCode|articleCodeSupplier': 'articleCode',
69
+ 'colorCode|colorCodeSupplier|colorSupplier': 'color',
70
+ 'size|sizeSupplier|mainSize|subSize|subSizeSupplier|_customsize': 'size',
71
+ 'articleGroup|articleGroupSupplier': 'articleGroup',
72
+ 'collection|collectionSupplier': 'collection',
73
+ 'brand|brandSupplier': 'brand',
74
+ '_gender|genderSupplier': 'gender',
75
+ };
76
+ // Letters, digits, and apostrophe stay; everything else is a delimiter.
77
+ const DELIMITERS = /[^\p{L}\p{N}']+/gu;
78
+ /** Shared stem: decode URI, then strip from the first known image extension onward. */
79
+ function filenameStem(fileName) {
80
+ let s;
81
+ try {
82
+ s = decodeURIComponent(fileName);
83
+ }
84
+ catch (_a) {
85
+ s = fileName;
86
+ }
87
+ s = s.toLowerCase();
88
+ const extIdx = s.search(/\.(?:jpe?g|png|gif)/i);
89
+ if (extIdx !== -1)
90
+ s = s.slice(0, extIdx);
91
+ return s;
92
+ }
93
+ function checkPattern(fieldVals, fileName) {
94
+ const fieldIndexes2 = {};
95
+ const sortedFields = fieldVals.sort((a, b) => {
96
+ return b.length - a.length;
97
+ });
98
+ // Return quickly if one of the fields in the pattern is not in the fileName
99
+ for (const fieldVal of sortedFields) {
100
+ const idx = fileName.indexOf(fieldVal);
101
+ if (idx == -1) {
102
+ return false;
103
+ }
104
+ fieldIndexes2[fieldVal] = [idx, idx + fieldVal.length];
105
+ }
106
+ // Check if all fields have a specific place in file name, with no overlap
107
+ for (let i = 1; i < sortedFields.length; i++) {
108
+ const startIndex = fieldIndexes2[sortedFields[i]][0];
109
+ const endIndex = fieldIndexes2[sortedFields[i]][1];
110
+ for (let j = 0; j < i; j++) {
111
+ const startIndex2 = fieldIndexes2[sortedFields[j]][0];
112
+ const endIndex2 = fieldIndexes2[sortedFields[j]][1];
113
+ if (startIndex < endIndex2 && endIndex > startIndex2) {
114
+ const nextIndex = fileName.indexOf(sortedFields[i], endIndex2);
115
+ if (nextIndex == -1) {
116
+ return false;
117
+ }
118
+ else {
119
+ fieldIndexes2[sortedFields[i]] = [nextIndex, nextIndex + sortedFields[i].length];
120
+ break;
121
+ }
122
+ }
123
+ }
124
+ }
125
+ return true;
126
+ }
127
+ function normalizeSkuField(sku, fieldName) {
128
+ let fieldVal = sku[fieldName];
129
+ if (fieldVal == null || fieldVal == '')
130
+ return '';
131
+ if (typeof fieldVal == 'string') {
132
+ return fieldVal.replace(DELIMITERS, '').toLowerCase();
133
+ }
134
+ else {
135
+ return fieldVal.toString();
136
+ }
137
+ }
138
+ function prepFilterSkusImagesAuto(filteredSkus, fileNames) {
67
139
  const skusCopy = (0, deepCopy_1.default)(filteredSkus);
68
140
  const preppedSkus = [];
69
141
  const fileNames2skip = [];
@@ -82,44 +154,100 @@ function prepFilterSkusImages(filteredSkus, merge = true, fileNames) {
82
154
  }
83
155
  const fieldsToBeMerged = {};
84
156
  for (const fieldName in sku) {
85
- if (fieldName == '__created' || fieldName == '__modified' || fieldName == 'images' || fieldName == 'brandhash' || (merge == true && fieldName == 'colorFamily'))
157
+ if (FIELDS_TO_SKIP.indexOf(fieldName) != -1 || fieldName.includes('Price'))
158
+ continue;
159
+ if (!(fieldName in sku) || sku[fieldName] == null || sku[fieldName] == undefined)
86
160
  continue;
87
- let fieldVal = sku[fieldName];
88
- if (fieldVal == '')
161
+ sku[fieldName] = normalizeSkuField(sku, fieldName);
162
+ const fieldVal = sku[fieldName];
163
+ if (!fieldVal)
89
164
  continue;
90
- if (typeof fieldVal == 'string') {
91
- sku[fieldName] = fieldVal.replace(/[^A-Za-zÀ-ÖØ-öø-ÿ0-9]/g, '').toLowerCase();
165
+ if (fieldVal in fieldsToBeMerged) {
166
+ fieldsToBeMerged[fieldVal].push(fieldName);
92
167
  }
93
168
  else {
94
- sku[fieldName] = fieldVal.toString();
169
+ fieldsToBeMerged[fieldVal] = [fieldName];
95
170
  }
96
- if (merge) {
97
- fieldVal = sku[fieldName];
98
- if (fieldVal in fieldsToBeMerged) {
99
- fieldsToBeMerged[fieldVal].push(fieldName);
100
- }
101
- else {
102
- fieldsToBeMerged[fieldVal] = [fieldName];
171
+ }
172
+ const newSku = {};
173
+ newSku['originalSku'] = filteredSkus[idx];
174
+ for (const fieldVal in fieldsToBeMerged) {
175
+ const fieldNameArray = fieldsToBeMerged[fieldVal].sort();
176
+ if (fieldNameArray.length > 1) {
177
+ const groups = {};
178
+ for (const fieldName of fieldNameArray) {
179
+ let included = false;
180
+ Object.keys(MERGE_GROUPS).forEach((group) => {
181
+ if (group.includes(fieldName)) {
182
+ included = true;
183
+ if (!groups[MERGE_GROUPS[group]])
184
+ groups[MERGE_GROUPS[group]] = fieldName;
185
+ else
186
+ groups[MERGE_GROUPS[group]] = groups[MERGE_GROUPS[group]] + '|' + fieldName;
187
+ }
188
+ });
189
+ if (!included) {
190
+ groups[fieldName] = fieldName;
191
+ }
103
192
  }
193
+ Object.keys(groups).forEach((group) => {
194
+ newSku[groups[group]] = fieldVal;
195
+ });
196
+ }
197
+ else {
198
+ newSku[fieldNameArray[0]] = fieldVal;
104
199
  }
105
200
  }
106
- if (!merge) {
201
+ preppedSkus.push(newSku);
202
+ });
203
+ const fileNames2Match = fileNames.filter((fileName) => {
204
+ return fileNames2skip.indexOf(fileName) == -1;
205
+ });
206
+ return { preppedSkus: preppedSkus, fileNames2Match: fileNames2Match };
207
+ }
208
+ function prepFilterSkusImagesManual(filteredSkus, fileNames, pattern) {
209
+ const skusCopy = (0, deepCopy_1.default)(filteredSkus);
210
+ const preppedSkus = [];
211
+ const fileNames2skip = [];
212
+ skusCopy.forEach((sku, idx) => {
213
+ if (sku.images && sku.images.length > 0) {
214
+ // If a fileName is already matched, we skip it
215
+ fileNames.forEach((fileName) => {
216
+ var _a;
217
+ if (((_a = sku.images) === null || _a === void 0 ? void 0 : _a.includes(fileName)) && fileNames2skip.indexOf(fileName) == -1)
218
+ fileNames2skip.push(fileName);
219
+ });
220
+ // If a sku has images, we skip it
221
+ sku['skip'] = true;
107
222
  preppedSkus.push(sku);
223
+ return;
108
224
  }
109
- else {
110
- const newSku = {};
111
- newSku['originalSku'] = filteredSkus[idx];
112
- for (const fieldVal in fieldsToBeMerged) {
113
- const fieldNameArray = fieldsToBeMerged[fieldVal].sort();
114
- if (fieldNameArray.length > 1) {
115
- newSku[fieldNameArray.join('|')] = fieldVal;
225
+ const foundValues = [];
226
+ for (const fieldName of pattern) {
227
+ if (!(fieldName in sku) || sku[fieldName] == null || sku[fieldName] == undefined) {
228
+ sku['skip'] = true;
229
+ break;
230
+ }
231
+ sku[fieldName] = normalizeSkuField(sku, fieldName);
232
+ if (!sku[fieldName]) {
233
+ sku['skip'] = true;
234
+ break;
235
+ }
236
+ for (const value of foundValues) {
237
+ if (value.includes(sku[fieldName]) || sku[fieldName] == value) {
238
+ if (!sku['subStrings'])
239
+ sku['subStrings'] = [];
240
+ sku['subStrings'].push([value, sku[fieldName]]);
116
241
  }
117
- else {
118
- newSku[fieldNameArray[0]] = fieldVal;
242
+ else if (sku[fieldName].includes(value)) {
243
+ if (!sku['subStrings'])
244
+ sku['subStrings'] = [];
245
+ sku['subStrings'].push([sku[fieldName], value]);
119
246
  }
120
247
  }
121
- preppedSkus.push(newSku);
248
+ foundValues.push(sku[fieldName]);
122
249
  }
250
+ preppedSkus.push(sku);
123
251
  });
124
252
  const fileNames2Match = fileNames.filter((fileName) => {
125
253
  return fileNames2skip.indexOf(fileName) == -1;
@@ -127,16 +255,9 @@ function prepFilterSkusImages(filteredSkus, merge = true, fileNames) {
127
255
  return { preppedSkus: preppedSkus, fileNames2Match: fileNames2Match };
128
256
  }
129
257
  function cleanFilename(fileName) {
130
- return (fileName
131
- // ── 1. strip any query string (everything from the first “?” to the end)
132
- .replace(/\?.*$/, '')
133
- // ── 2. strip a terminal image extension (case‑insensitive) .jpeg, .jpg, .png or .gif
134
- .replace(/\.(?:jpe?g|png|gif)$/i, '')
135
- // ── 3. drop every character that is NOT a letter (Unicode!) or a digit
136
- .replace(/[^\p{L}\p{N}]+/gu, '')
137
- .toLowerCase());
258
+ return filenameStem(fileName).replace(DELIMITERS, '');
138
259
  }
139
- function checkWithoutLast(fileName) {
260
+ function removeLastToken(fileName) {
140
261
  // If last token equals any of the following patterns, we will check the fileName without the last token
141
262
  // 1) includes front, back, side or sole
142
263
  // 2) is 1 alphanumerical character
@@ -146,11 +267,9 @@ function checkWithoutLast(fileName) {
146
267
  // 5) ends with a number
147
268
  // 6) ends with two numbers
148
269
  // If any of those checks are true, we return the fileName without the last token
149
- // Last token here is defined as the last token in the fileName, split by underscores, dashes, dots, spaces or URL encoded characters (like %20)
150
- const tokens = fileName
151
- .toLowerCase()
152
- .split(/[_\-.\s()]+|%[0-9A-Fa-f]{2}/)
153
- .slice(0, -1);
270
+ // Tokens come from the shared stem (decode + extension strip), split on anything that is
271
+ // not a letter, digit, or apostrophe — same rule as cleanFilename.
272
+ const tokens = filenameStem(fileName).split(DELIMITERS).filter(Boolean);
154
273
  const lastToken = tokens[tokens.length - 1];
155
274
  let returnWithoutLast = false;
156
275
  if (lastToken) {
@@ -165,42 +284,27 @@ function checkWithoutLast(fileName) {
165
284
  const tokenLength = tokens.length;
166
285
  if (returnWithoutLast) {
167
286
  let sliceIndex = -1;
168
- if (tokenLength > 3 && tokens[tokenLength - 3] == 'press' && ((tokens[tokenLength - 2] == 'pack') || (tokens[tokenLength - 2] == 'model')))
287
+ if (tokenLength > 3 && tokens[tokenLength - 3] == 'press' && (tokens[tokenLength - 2] == 'pack' || tokens[tokenLength - 2] == 'model'))
169
288
  sliceIndex = -3;
170
289
  else if (tokenLength > 2 && tokens[tokenLength - 2] == 'packshot')
171
290
  sliceIndex = -2;
172
- return tokens
173
- .slice(0, sliceIndex)
174
- .join('')
175
- .replace(/[^\p{L}\p{N}]+/gu, '');
291
+ return tokens.slice(0, sliceIndex).join('');
176
292
  }
177
- if (tokenLength > 2 && tokens[tokenLength - 2] == 'press' && ((tokens[tokenLength - 1] == 'pack') || (tokens[tokenLength - 1] == 'model'))) {
178
- return tokens
179
- .slice(0, -2)
180
- .join('')
181
- .replace(/[^\p{L}\p{N}]+/gu, '');
293
+ if (tokenLength > 2 && tokens[tokenLength - 2] == 'press' && (tokens[tokenLength - 1] == 'pack' || tokens[tokenLength - 1] == 'model')) {
294
+ return tokens.slice(0, -2).join('');
182
295
  }
183
296
  else if (tokenLength > 1 && tokens[tokenLength - 1] == 'packshot') {
184
- return tokens
185
- .slice(0, -1)
186
- .join('')
187
- .replace(/[^\p{L}\p{N}]+/gu, '');
297
+ return tokens.slice(0, -1).join('');
188
298
  }
189
299
  if (lastToken.includes('product')) {
190
- const joinedCleanFileName = tokens.join('').replace(/[^\p{L}\p{N}]+/gu, '');
300
+ const joinedCleanFileName = tokens.join('');
191
301
  return joinedCleanFileName.slice(0, joinedCleanFileName.indexOf('product'));
192
302
  }
193
303
  else if (Number(lastToken.slice(-1))) {
194
- return tokens
195
- .join('')
196
- .replace(/[^\p{L}\p{N}]+/gu, '')
197
- .slice(0, -1);
304
+ return tokens.join('').slice(0, -1);
198
305
  }
199
306
  else if (Number(lastToken.slice(-2))) {
200
- return tokens
201
- .join('')
202
- .replace(/[^\p{L}\p{N}]+/gu, '')
203
- .substring(0, -2);
307
+ return tokens.join('').substring(0, -2);
204
308
  }
205
309
  }
206
310
  return '';
@@ -413,7 +517,6 @@ function fallbackSearch(cleanedFileName, fileNameWithoutLast, preppedSkus) {
413
517
  }
414
518
  }
415
519
  function imageBinder(fileNames, filteredSkus, brand, specifiedPattern, test = false) {
416
- const totalLength = fileNames.length;
417
520
  let count = 1;
418
521
  const fileNameIndexMatch = {};
419
522
  let quickCount = 0;
@@ -422,36 +525,109 @@ function imageBinder(fileNames, filteredSkus, brand, specifiedPattern, test = fa
422
525
  let newPatterns = {};
423
526
  if (specifiedPattern[0] != 'auto') {
424
527
  // If the user/administrator provides a pattern, we use that by default
425
- const prepObject = prepFilterSkusImages(filteredSkus, false, fileNames);
528
+ const prepObject = prepFilterSkusImagesManual(filteredSkus, fileNames, specifiedPattern);
426
529
  const preppedSkus = prepObject.preppedSkus;
427
530
  const fileNames2Match = prepObject.fileNames2Match;
428
531
  fileNames2Match.forEach((fileName) => {
429
532
  const cleanedFileName = cleanFilename(fileName);
430
- const fileNameWithoutLast = checkWithoutLast(fileName);
431
533
  fileNameIndexMatch[fileName] = [];
534
+ const matchingCombinations = {};
535
+ // Check which skus match the specified pattern and store matching fields (combinations)
432
536
  preppedSkus.forEach((sku, idx) => {
433
537
  if (sku['skip'])
434
538
  return;
435
539
  let skuStillMatches = true;
540
+ let combinationArray = [];
436
541
  specifiedPattern.every((fieldName) => {
437
- if (!(fieldName in sku))
542
+ const fieldVal = sku[fieldName];
543
+ if (!cleanedFileName.includes(fieldVal))
438
544
  skuStillMatches = false;
439
- else {
440
- const fieldVal = sku[fieldName];
441
- if (!cleanedFileName.includes(fieldVal) && !fileNameWithoutLast.includes(fieldVal))
442
- skuStillMatches = false;
443
- }
545
+ else
546
+ combinationArray.push(fieldVal);
444
547
  return skuStillMatches;
445
548
  });
446
549
  if (skuStillMatches) {
447
- fileNameIndexMatch[fileName].push(idx);
550
+ let combination = combinationArray.join('|');
551
+ if (combination in matchingCombinations) {
552
+ matchingCombinations[combination].push(idx);
553
+ }
554
+ else {
555
+ matchingCombinations[combination] = [idx];
556
+ }
448
557
  }
449
558
  });
559
+ let matchingIndices = [];
560
+ // If only one combination of specified pattern matches, we use it
561
+ if (Object.keys(matchingCombinations).length == 1) {
562
+ matchingIndices = matchingCombinations[Object.keys(matchingCombinations)[0]];
563
+ }
564
+ else {
565
+ // In case of multiple combinations, do substring check
566
+ // -> Checks if all fields have a specific place in file name, with no overlap
567
+ const matchesAfterSubstringCheck = {};
568
+ Object.keys(matchingCombinations).forEach((combination) => {
569
+ const combinationArray = combination.split('|');
570
+ const isMatch = checkPattern(combinationArray, cleanedFileName);
571
+ if (isMatch) {
572
+ matchesAfterSubstringCheck[combination] = matchingCombinations[combination];
573
+ }
574
+ });
575
+ // If only one combination is left after substring check, we use it
576
+ if (Object.keys(matchesAfterSubstringCheck).length == 1) {
577
+ matchingIndices = matchesAfterSubstringCheck[Object.keys(matchesAfterSubstringCheck)[0]];
578
+ }
579
+ else {
580
+ // In case there are still multiple combinations, we check longest combinations
581
+ // -> Longest combinations are more specific and therefore preferred
582
+ let lengthLongest = 0;
583
+ let longestCombinations = [];
584
+ Object.keys(matchesAfterSubstringCheck).forEach((combination) => {
585
+ if (combination.length > lengthLongest) {
586
+ lengthLongest = combination.length;
587
+ longestCombinations = [combination];
588
+ }
589
+ else if (combination.length == lengthLongest) {
590
+ longestCombinations.push(combination);
591
+ }
592
+ });
593
+ // If only one combination is left after longest combination check, we use it
594
+ if (longestCombinations.length == 1) {
595
+ matchingIndices = matchesAfterSubstringCheck[longestCombinations[0]];
596
+ }
597
+ else {
598
+ // In case there are still multiple combinations, we check fileNameWithoutLast
599
+ // -> If any combination matches fileNameWithoutLast, we prefer it, as last tokens can be junk
600
+ const fileNameWithoutLast = removeLastToken(fileName);
601
+ const matchesWithoutLast = {};
602
+ if (fileNameWithoutLast) {
603
+ longestCombinations.forEach((combination) => {
604
+ const combinationArray = combination.split('|');
605
+ if (checkPattern(combinationArray, fileNameWithoutLast)) {
606
+ matchesWithoutLast[combination] = matchesAfterSubstringCheck[combination];
607
+ }
608
+ });
609
+ }
610
+ // If only one combination is left after fileNameWithoutLast check, we use it
611
+ if (Object.keys(matchesWithoutLast).length == 1) {
612
+ matchingIndices = matchesWithoutLast[Object.keys(matchesWithoutLast)[0]];
613
+ }
614
+ else {
615
+ Object.keys(matchesAfterSubstringCheck).forEach((combination) => {
616
+ matchingIndices.push(...matchesAfterSubstringCheck[combination]);
617
+ });
618
+ }
619
+ }
620
+ }
621
+ }
622
+ if (matchingIndices.length > 0)
623
+ fileNameIndexMatch[fileName] = matchingIndices;
624
+ if (!fileNameIndexMatch[fileName])
625
+ fileNameIndexMatch[fileName] = [];
450
626
  });
451
627
  }
452
628
  else {
453
629
  // Else, we look for patterns ourselves using a couple of methods, prioritized on efficiency
454
- const prepObject = prepFilterSkusImages(filteredSkus, true, fileNames);
630
+ const prepObject = prepFilterSkusImagesAuto(filteredSkus, fileNames);
455
631
  const preppedSkus = prepObject.preppedSkus;
456
632
  const fileNames2Match = prepObject.fileNames2Match;
457
633
  let quickSearch = prepQuickSearch(preppedSkus);
@@ -459,7 +635,7 @@ function imageBinder(fileNames, filteredSkus, brand, specifiedPattern, test = fa
459
635
  fileNames2Match.forEach((fileName) => {
460
636
  let matchFound = false;
461
637
  const cleanedFileName = cleanFilename(fileName);
462
- const fileNameWithoutLast = checkWithoutLast(fileName);
638
+ const fileNameWithoutLast = removeLastToken(fileName);
463
639
  let indicesForFileName = [];
464
640
  // Method #1: try quicksearch, an object with patterns as keys and arrays of [proposedFilename, sku] as values
465
641
  // based on existing patterns in GLOBAL_PATTERNS
@@ -524,7 +700,7 @@ function onlySaveIDFromSkus(matchDict) {
524
700
  }
525
701
  return out;
526
702
  }
527
- function testBatch(brand) {
703
+ function testBatch(brand, specifiedPattern = ['auto']) {
528
704
  return __awaiter(this, void 0, void 0, function* () {
529
705
  var _a, _b, _c;
530
706
  const [skusModule, imagesModule, matchedModule] = yield Promise.all([
@@ -537,7 +713,7 @@ function testBatch(brand) {
537
713
  const expectedRaw = matchedModule.default;
538
714
  // 1) Call imageBinder
539
715
  const t0 = Date.now();
540
- const imageBinderResult = imageBinder(fileNames, filteredSkus, brand, ['auto'], true);
716
+ const imageBinderResult = imageBinder(fileNames, filteredSkus, brand, specifiedPattern, true);
541
717
  const t1 = Date.now();
542
718
  const matchIndices = imageBinderResult.match || {};
543
719
  // 2) Convert indices → actual SKU arrays → ID arrays
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwear/latestcollectioncore",
3
- "version": "1.0.202",
3
+ "version": "1.0.204",
4
4
  "description": "Core functions for LatestCollections applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -108,7 +108,7 @@ function propFunction(propertyName: string, getPropertyName: Function, obj: Reco
108
108
  const path = propertyName.substring(positionOfDot + 1).split('.')
109
109
  if (table == 'transaction') return resolvePath(obj, path)
110
110
  const joinedRecord = globalRelations[table]?.[obj[globalJoinableTables[table]]]
111
- return resolvePath(joinedRecord, path) ?? '~ ?'
111
+ return resolvePath(joinedRecord, path) || '~ ?'
112
112
  }
113
113
 
114
114
  const lower = (s: string) => s.toLowerCase()
@@ -153,18 +153,9 @@ function addTransactionToAggregations(beginTimestamp: number, endTimestamp: numb
153
153
  aggregateVector[QTY_RETURN] += v[QTY_SOLD] < 0 ? v[QTY_SOLD] : 0
154
154
  aggregateVector[AMOUNT_RETURN] += v[AMOUNT_SOLD_EXCL] < 0 ? v[AMOUNT_SOLD_EXCL] : 0
155
155
 
156
- aggregateVector[STOCK_TIME_PRODUCT] +=
157
- ((v[QTY_RECIEVED] || 0) + (v[QTY_TRANSIT] || 0) + (v[QTY_CHANGE] || 0) - (v[QTY_SOLD] || 0) - (v[QTY_B2B_SOLD] || 0) - (v[QTY_B2B_RESOLD] || 0) + (v[QTY_B2B_RETURN] || 0)) *
158
- (endTimestamp - transaction.time)
156
+ aggregateVector[STOCK_TIME_PRODUCT] += ((v[QTY_RECIEVED] || 0) + (v[QTY_TRANSIT] || 0) + (v[QTY_CHANGE] || 0) - (v[QTY_SOLD] || 0) - (v[QTY_B2B_SOLD] || 0) - (v[QTY_B2B_RESOLD] || 0) + (v[QTY_B2B_RETURN] || 0)) * (endTimestamp - transaction.time)
159
157
  aggregateVector[STOCKAMOUNT_TIME_PRODUCT] +=
160
- ((v[AMOUNT_RECIEVED] || 0) +
161
- (v[AMOUNT_TRANSIT] || 0) +
162
- (v[AMOUNT_CHANGE] || 0) -
163
- (v[COSTPRICE_SOLD] || 0) -
164
- (v[COSTPRICE_B2B_SOLD] || 0) -
165
- (v[COSTPRICE_B2B_RESOLD] || 0) +
166
- (v[COSTPRICE_B2B_RETURN] || 0)) *
167
- (endTimestamp - transaction.time)
158
+ ((v[AMOUNT_RECIEVED] || 0) + (v[AMOUNT_TRANSIT] || 0) + (v[AMOUNT_CHANGE] || 0) - (v[COSTPRICE_SOLD] || 0) - (v[COSTPRICE_B2B_SOLD] || 0) - (v[COSTPRICE_B2B_RESOLD] || 0) + (v[COSTPRICE_B2B_RETURN] || 0)) * (endTimestamp - transaction.time)
168
159
  }
169
160
 
170
161
  function buildAggregationEvaluator(aggregationExpression: string): Function {
@@ -222,17 +213,7 @@ function getTrieLeaf<T extends { children?: Map<unknown, T> }>(root: T, transact
222
213
  *
223
214
  * @returns {Function} - The aggregator function
224
215
  */
225
- function aggregator({
226
- beginTimestamp,
227
- endTimestamp,
228
- filterExpression,
229
- aggregationExpression,
230
- rawAggregations,
231
- maxRows,
232
- totals,
233
- subtotalsForOuterGrouper,
234
- parentGroupTotals,
235
- }: AggregatorFnI) {
216
+ function aggregator({ beginTimestamp, endTimestamp, filterExpression, aggregationExpression, rawAggregations, maxRows, totals, subtotalsForOuterGrouper, parentGroupTotals }: AggregatorFnI) {
236
217
  let filter: ((transaction: TransactionI) => unknown) | undefined
237
218
  let filterGranularity: string[] = []
238
219
  const filterTrieRoot: FilterResultTrieNode = {}
@@ -303,20 +284,7 @@ function aggregator({
303
284
  }
304
285
  }
305
286
 
306
- function aggregate({
307
- beginTimestamp,
308
- endTimestamp,
309
- filterExpression,
310
- aggregationExpression,
311
- transactions,
312
- beginStock,
313
- relations,
314
- rawAggregations,
315
- maxRows,
316
- totals,
317
- subtotalsForOuterGrouper,
318
- parentGroupTotals,
319
- }: AggregateFnI) {
287
+ function aggregate({ beginTimestamp, endTimestamp, filterExpression, aggregationExpression, transactions, beginStock, relations, rawAggregations, maxRows, totals, subtotalsForOuterGrouper, parentGroupTotals }: AggregateFnI) {
320
288
  globalNrAggegationExpressions = 0
321
289
  globalRelations = relations
322
290
 
@@ -353,8 +321,7 @@ function whichShardsToProcess(dateRange: any, granularity: 'year' | 'month'): an
353
321
  else
354
322
  for (let year = dateRange.utcFromYear; year <= dateRange.utcToYear; year++) {
355
323
  if (year == dateRange.utcFromYear && year < dateRange.utcToYear) for (let month = dateRange.utcFromMonth; month <= 12; month++) result.push('' + year + '' + padl(month))
356
- if (year == dateRange.utcFromYear && year == dateRange.utcToYear)
357
- for (let month = dateRange.utcFromMonth; month <= dateRange.utcToMonth; month++) result.push('' + year + '' + padl(month))
324
+ if (year == dateRange.utcFromYear && year == dateRange.utcToYear) for (let month = dateRange.utcFromMonth; month <= dateRange.utcToMonth; month++) result.push('' + year + '' + padl(month))
358
325
  if (year != dateRange.utcFromYear && year != dateRange.utcToYear) for (let month = 1; month <= 12; month++) result.push('' + year + '' + padl(month))
359
326
  if (year != dateRange.utcFromYear && year == dateRange.utcToYear) for (let month = 1; month <= dateRange.utcToMonth; month++) result.push('' + year + '' + padl(month))
360
327
  }
@@ -378,8 +345,7 @@ function postAgg(v: any, timeFrame: number): void {
378
345
  // - beginStock from type 0 (same as QTY_STOCK)
379
346
  // - beginShelfStock adjustment from type 10 (negative picktickets from previous shards)
380
347
  v[QTY_END_SHELF_STOCK] = v[QTY_SHELF_STOCK] + v[QTY_RECIEVED] + v[QTY_CHANGE] + v[QTY_TRANSIT] - totalQtySold - v[QTY_CONSIGNMENT] - v[QTY_PICKLIST]
381
- v[AMOUNT_END_SHELF_STOCK] =
382
- v[AMOUNT_SHELF_STOCK] + v[AMOUNT_RECIEVED] + v[AMOUNT_CHANGE] + v[AMOUNT_TRANSIT] + v[AMOUNT_REVALUATE] - totalCostSold - v[AMOUNT_CONSIGNMENT] - v[AMOUNT_PICKLIST]
348
+ v[AMOUNT_END_SHELF_STOCK] = v[AMOUNT_SHELF_STOCK] + v[AMOUNT_RECIEVED] + v[AMOUNT_CHANGE] + v[AMOUNT_TRANSIT] + v[AMOUNT_REVALUATE] - totalCostSold - v[AMOUNT_CONSIGNMENT] - v[AMOUNT_PICKLIST]
383
349
  v[SELLOUT_PERCENTAGE] = (totalQtySold / (totalQtySold + v[QTY_END_STOCK])) * 100
384
350
  v[ROI] = totalAmountSold - v[AMOUNT_RECIEVED] + v[AMOUNT_TRANSIT]
385
351
  v[QTY_AVG_STOCK] = (v[QTY_STOCK] * timeFrame + v[STOCK_TIME_PRODUCT]) / timeFrame