@softwear/latestcollectioncore 1.0.76 → 1.0.78

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.
@@ -0,0 +1,79 @@
1
+ import { TransactionI, RaGI, HrTimeframeI, TimeGranularityE, SkuI } from './types';
2
+ /**
3
+ * The propFunction for FiltreX needs some data that can't be passed as parameters. That's why the 'globalsRelations' variable is needed.
4
+ * TODO: Functional: Experiment with curried functions
5
+ */
6
+ declare function propFunction(propertyName: string, getPropertyName: Function, obj: Record<string, any>): any;
7
+ declare function whichShardsToProcess(dateRange: any, granularity: 'year' | 'month'): any[];
8
+ /**
9
+ *
10
+ * @param {*} v: a vector
11
+ * @param {*} timeFrame: nr miliseconds in timeframe
12
+ *
13
+ * Calculate all derived columns on a given vector.
14
+ */
15
+ declare function postAgg(v: any, timeFrame: number): void;
16
+ declare function getTimeGrouper(groupers: string[]): TimeGranularityE | undefined;
17
+ declare function runQuery(timeframe: HrTimeframeI, rawAggregations: RaGI, beginStock: boolean, transactions: TransactionI[], selectedGroupsValues: string[], skus: SkuI[], warehouses: any[], subtotalsForOuterGrouper: boolean, computedFilter: string, parentGroupTotals: boolean): RaGI;
18
+ declare const _default: {
19
+ getDateRangeFromPicker: (inputDates: string[]) => HrTimeframeI;
20
+ getTimeGrouper: typeof getTimeGrouper;
21
+ filtrexOptions: {
22
+ extraFunctions: {
23
+ lower: (s: string) => string;
24
+ upper: (s: string) => string;
25
+ };
26
+ customProp: typeof propFunction;
27
+ };
28
+ postAgg: typeof postAgg;
29
+ runQuery: typeof runQuery;
30
+ whichShardsToProcess: typeof whichShardsToProcess;
31
+ AMOUNT_CHANGE: number;
32
+ AMOUNT_CONSIGNMENT: number;
33
+ AMOUNT_END_SHELF_STOCK: number;
34
+ AMOUNT_END_STOCK: number;
35
+ AMOUNT_MINIMUM_STOCK: number;
36
+ AMOUNT_PO: number;
37
+ AMOUNT_PO_COMPLETE: number;
38
+ AMOUNT_RECIEVED: number;
39
+ AMOUNT_RETURN: number;
40
+ AMOUNT_REVALUATE: number;
41
+ AMOUNT_SHELF_STOCK: number;
42
+ AMOUNT_SOLD: number;
43
+ AMOUNT_SOLD_DISCOUNT: number;
44
+ AMOUNT_SOLD_EXCL: number;
45
+ AMOUNT_SOLD_MAX: number;
46
+ AMOUNT_STOCK: number;
47
+ AMOUNT_TRANSIT: number;
48
+ AMOUNT_TURNOVER_VELOCITY: number;
49
+ COSTPRICE_CONSIGNMENT: number;
50
+ COSTPRICE_SOLD: number;
51
+ MARGIN: number;
52
+ MAX_RECIEVE_TIMESTAMP: number;
53
+ PROFIT: number;
54
+ PROFITABILITY: number;
55
+ QTY_AVG_STOCK: number;
56
+ QTY_CHANGE: number;
57
+ QTY_CONSIGNMENT: number;
58
+ QTY_END_SHELF_STOCK: number;
59
+ QTY_END_STOCK: number;
60
+ QTY_MINIMUM_STOCK: number;
61
+ QTY_PO: number;
62
+ QTY_PO_COMPLETE: number;
63
+ QTY_RECIEVED: number;
64
+ QTY_RETURN: number;
65
+ QTY_SHELF_STOCK: number;
66
+ QTY_SOLD: number;
67
+ QTY_SOLD_BEFORE_RETURNS: number;
68
+ QTY_STOCK: number;
69
+ QTY_TRANSACTION: number;
70
+ QTY_TRANSIT: number;
71
+ QTY_TURNOVER_VELOCITY: number;
72
+ RETURN_PERCENTAGE: number;
73
+ ROI: number;
74
+ SELLOUT_PERCENTAGE: number;
75
+ STOCKAMOUNT_TIME_PRODUCT: number;
76
+ STOCK_TIME_PRODUCT: number;
77
+ VALUE_AVG_STOCK: number;
78
+ };
79
+ export default _default;
@@ -0,0 +1,414 @@
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 filtrex_1 = require("filtrex");
7
+ const types_1 = require("./types");
8
+ const transaction_1 = __importDefault(require("./transaction"));
9
+ const date_fns_1 = require("date-fns");
10
+ // fields in Vector
11
+ const { QTY_TRANSACTION, QTY_STOCK, AMOUNT_STOCK, QTY_SHELF_STOCK, AMOUNT_SHELF_STOCK, AMOUNT_REVALUATE, QTY_RECIEVED, AMOUNT_RECIEVED, QTY_SOLD, AMOUNT_SOLD, AMOUNT_SOLD_EXCL, COSTPRICE_SOLD, QTY_CHANGE, AMOUNT_CHANGE, QTY_TRANSIT, AMOUNT_TRANSIT, QTY_PO, AMOUNT_PO, QTY_PO_COMPLETE, AMOUNT_PO_COMPLETE, QTY_MINIMUM_STOCK, AMOUNT_MINIMUM_STOCK, QTY_CONSIGNMENT, AMOUNT_CONSIGNMENT, COSTPRICE_CONSIGNMENT, } = transaction_1.default.transactionVector;
12
+ // DERIVED FIELDS
13
+ const AMOUNT_SOLD_MAX = 25;
14
+ const AMOUNT_SOLD_DISCOUNT = 26;
15
+ const MAX_RECIEVE_TIMESTAMP = 27;
16
+ const STOCK_TIME_PRODUCT = 28;
17
+ const STOCKAMOUNT_TIME_PRODUCT = 29;
18
+ const QTY_RETURN = 30;
19
+ const AMOUNT_RETURN = 31;
20
+ // Vector length to allocate for aggregation
21
+ const VECTOR_LENGTH = 32;
22
+ // POST AGGREGATION COLUMNS
23
+ const SELLOUT_PERCENTAGE = 32;
24
+ const ROI = 33;
25
+ const QTY_AVG_STOCK = 34;
26
+ const VALUE_AVG_STOCK = 35;
27
+ const QTY_TURNOVER_VELOCITY = 36;
28
+ const AMOUNT_TURNOVER_VELOCITY = 37;
29
+ const PROFITABILITY = 38;
30
+ const MARGIN = 39;
31
+ const PROFIT = 40;
32
+ const QTY_END_SHELF_STOCK = 41;
33
+ const AMOUNT_END_SHELF_STOCK = 42;
34
+ const QTY_END_STOCK = 43;
35
+ const AMOUNT_END_STOCK = 44;
36
+ const RETURN_PERCENTAGE = 45;
37
+ const QTY_SOLD_BEFORE_RETURNS = 46;
38
+ const BEGIN_STOCK_VECTOR_START = QTY_STOCK;
39
+ const BEGIN_STOCK_VECTOR_END = AMOUNT_SHELF_STOCK + 1;
40
+ const msYear = 24 * 3600 * 365 * 1000;
41
+ const globalJoinableTables = { sku: 'ean', wh: 'wh', transaction: 'row' }; // defines the keys we can join on
42
+ /**
43
+ * Global variables
44
+ */
45
+ let globalRelations = {}; // holds the joined tables
46
+ let globalNrAggegationExpressions = 0; // tracks the # aggregations rows so we can quit computing if it grows out of bounds
47
+ /**
48
+ * The propFunction for FiltreX needs some data that can't be passed as parameters. That's why the 'globalsRelations' variable is needed.
49
+ * TODO: Functional: Experiment with curried functions
50
+ */
51
+ function propFunction(propertyName, getPropertyName, obj) {
52
+ var _a, _b;
53
+ const positionOfDot = propertyName.indexOf('.');
54
+ if (positionOfDot < 0)
55
+ return getPropertyName(propertyName);
56
+ const table = propertyName.substring(0, positionOfDot);
57
+ const field = propertyName.substring(positionOfDot + 1);
58
+ if (table == 'transaction')
59
+ return getPropertyName(field);
60
+ return ((_b = (_a = globalRelations[table]) === null || _a === void 0 ? void 0 : _a[obj[globalJoinableTables[table]]]) === null || _b === void 0 ? void 0 : _b[field]) || '~ ?';
61
+ }
62
+ const lower = (s) => s.toLowerCase();
63
+ const upper = (s) => s.toUpperCase();
64
+ const filtrexOptions = {
65
+ extraFunctions: { lower, upper },
66
+ customProp: propFunction,
67
+ };
68
+ // This is how the actual joining of 2 vectors happens
69
+ function addVectors(targetVector, otherVector, begin, end) {
70
+ for (let i = begin; i < end; i++)
71
+ targetVector[i] += otherVector[i] || 0;
72
+ }
73
+ // This function takes a transaction and adds it to the aggregations
74
+ function addTransactionToAggregations(beginTimestamp, endTimestamp, aggregateKey, rawAggregations, transaction, maxRows) {
75
+ let aggregateVector = rawAggregations[aggregateKey];
76
+ if (globalNrAggegationExpressions >= maxRows)
77
+ return;
78
+ if (!aggregateVector) {
79
+ globalNrAggegationExpressions++;
80
+ aggregateVector = new Float64Array(VECTOR_LENGTH);
81
+ rawAggregations[aggregateKey] = aggregateVector;
82
+ }
83
+ const vector = transaction.vector;
84
+ if (transaction.type == '1' && transaction.time > aggregateVector[MAX_RECIEVE_TIMESTAMP])
85
+ aggregateVector[MAX_RECIEVE_TIMESTAMP] = transaction.time;
86
+ if (transaction.time <= beginTimestamp)
87
+ return addVectors(aggregateVector, vector, BEGIN_STOCK_VECTOR_START, BEGIN_STOCK_VECTOR_END);
88
+ if (transaction.time > endTimestamp)
89
+ return;
90
+ addVectors(aggregateVector, vector, BEGIN_STOCK_VECTOR_END, vector.length);
91
+ // Calculate derived fields
92
+ aggregateVector[QTY_RETURN] += vector[QTY_SOLD] < 0 ? vector[QTY_SOLD] : 0;
93
+ aggregateVector[AMOUNT_RETURN] += vector[AMOUNT_SOLD_EXCL] < 0 ? vector[AMOUNT_SOLD_EXCL] : 0;
94
+ aggregateVector[STOCK_TIME_PRODUCT] +=
95
+ (vector[QTY_RECIEVED] || 0 + vector[QTY_TRANSIT] || 0 + vector[QTY_CHANGE] || 0 - vector[QTY_SOLD] || 0) * (endTimestamp - transaction.time);
96
+ aggregateVector[STOCKAMOUNT_TIME_PRODUCT] +=
97
+ (vector[AMOUNT_RECIEVED] || 0 + vector[AMOUNT_TRANSIT] || 0 + vector[AMOUNT_CHANGE] || 0 - vector[COSTPRICE_SOLD] || 0) * (endTimestamp - transaction.time);
98
+ }
99
+ function buildAggregationEvaluator(aggregationExpression) {
100
+ return (0, filtrex_1.compileExpression)(aggregationExpression, filtrexOptions);
101
+ }
102
+ function prepareSubTotalAggKey(aggKey) {
103
+ return aggKey.split('\t').slice(0, -1).join('\t').concat(' Σ');
104
+ }
105
+ /*
106
+ * A factory function that returns a function that can be used to aggregate transactions
107
+ * The factory function is called once for each aggregationExpression
108
+ * @param {Object} options
109
+ *
110
+ * @param {number} options.beginTimestamp - The begin of the time frame UNIX timestamp
111
+ * @param {number} options.endTimestamp - The end of the time frame UNIX timestamp
112
+ *
113
+ * @param {string} options.filterExpression - The FiltreX preprocessed filter expression for: brand, collection, warehouse and articleGroup
114
+ * Example: sku.brand in ("Gabor") and sku.collection not in ("21summer")
115
+ *
116
+ * @param {string} options.aggregationExpression - The aggregation expression that contains the aggregateKey
117
+ * Example: sku.brand+" "+sku.collection+" "+sku.articleGroup+" "+sku.articleCodeSupplier
118
+ *
119
+ * @param {Object} options.rawAggregations - The raw aggregations object where keys are the aggregateKeys and values are the Float64Array vectors
120
+ *
121
+ * @param {number} options.maxRows - The maximum number of rows to aggregate
122
+ * @param {boolean} options.totals - Whether to aggregate totals
123
+ * @param {boolean} options.subtotalsForOuterGrouper - Whether to aggregate single subtotals
124
+ *
125
+ * @returns {Function} - The aggregator function
126
+ */
127
+ function aggregator({ beginTimestamp, endTimestamp, filterExpression, aggregationExpression, rawAggregations, maxRows, totals, subtotalsForOuterGrouper, parentGroupTotals, }) {
128
+ const filterExpressionCache = {};
129
+ let filter;
130
+ let filterDependencyEvaluator;
131
+ if (filterExpression) {
132
+ filter = (0, filtrex_1.compileExpression)(filterExpression, filtrexOptions);
133
+ // Maintain a cache of evaluated filterExpressions
134
+ // The granularity of the cache is determined by the joined tables in the filterExpression
135
+ const filterGranularity = Object.entries(globalJoinableTables)
136
+ .filter((join) => filterExpression.includes(join[0] + '.'))
137
+ .map((join) => join[1]);
138
+ filterDependencyEvaluator = (0, filtrex_1.compileExpression)(filterGranularity.join('+'));
139
+ }
140
+ const aggregationEvaluator = buildAggregationEvaluator(aggregationExpression);
141
+ // Maintain a cache of evaluated aggregationExpressions
142
+ // The granularity of the cache is determined by the joined tables in the aggregationExpression
143
+ const aggregationExpressionCache = {};
144
+ const aggregationGranularity = Object.entries(globalJoinableTables)
145
+ .filter((join) => aggregationExpression.includes(join[0] + '.'))
146
+ .map((join) => join[1]);
147
+ let aggregationDependencyExpression = aggregationGranularity.join('+');
148
+ if (!aggregationDependencyExpression)
149
+ aggregationDependencyExpression = '"N.A."';
150
+ const aggregationDependencyEvaluator = (0, filtrex_1.compileExpression)(aggregationDependencyExpression);
151
+ return function (transaction) {
152
+ if (filter) {
153
+ const filterDependency = filterDependencyEvaluator(transaction);
154
+ const filterExpression = filterExpressionCache[filterDependency];
155
+ if (filterExpression == -1)
156
+ return;
157
+ if (!filterExpression) {
158
+ filterExpressionCache[filterDependency] = filter(transaction) ? 1 : -1;
159
+ if (filterExpressionCache[filterDependency] == -1)
160
+ return;
161
+ }
162
+ }
163
+ const aggregationDependency = aggregationDependencyEvaluator(transaction);
164
+ let aggregateKey = aggregationExpressionCache[aggregationDependency];
165
+ if (!aggregateKey) {
166
+ aggregateKey = aggregationEvaluator(transaction);
167
+ aggregationExpressionCache[aggregationDependency] = aggregateKey;
168
+ }
169
+ // Prepare joined values for derived fields
170
+ if (transaction.type == '2') {
171
+ const sku = globalRelations['sku'][transaction.ean];
172
+ const vector = transaction.vector;
173
+ vector[AMOUNT_SOLD_MAX] = ((sku === null || sku === void 0 ? void 0 : sku.price) || 0) * vector[QTY_SOLD];
174
+ vector[AMOUNT_SOLD_DISCOUNT] = vector[AMOUNT_SOLD_MAX] - vector[AMOUNT_SOLD];
175
+ }
176
+ if (totals)
177
+ addTransactionToAggregations(beginTimestamp, endTimestamp, '** TOTAL **', rawAggregations, transaction, maxRows);
178
+ // Single subtotals are subtotals for first grouper level
179
+ // Example: for 4 groupers we have rows like
180
+ // g0 g1 g2 g3 <- normal row with no subtotal
181
+ // g0 <- subtotal for g0
182
+ if (subtotalsForOuterGrouper == true) {
183
+ addTransactionToAggregations(beginTimestamp, endTimestamp, `${aggregateKey.substr(0, aggregateKey.indexOf('\t'))} Σ`, rawAggregations, transaction, maxRows);
184
+ }
185
+ if (parentGroupTotals) {
186
+ const KPIKey = prepareSubTotalAggKey(aggregateKey);
187
+ addTransactionToAggregations(beginTimestamp, endTimestamp, KPIKey, rawAggregations, transaction, maxRows);
188
+ }
189
+ else {
190
+ addTransactionToAggregations(beginTimestamp, endTimestamp, aggregateKey, rawAggregations, transaction, maxRows);
191
+ }
192
+ };
193
+ }
194
+ function aggregate({ beginTimestamp, endTimestamp, filterExpression, aggregationExpression, transactions, beginStock, relations, rawAggregations, maxRows, totals, subtotalsForOuterGrouper, parentGroupTotals, }) {
195
+ globalNrAggegationExpressions = 0;
196
+ globalRelations = relations;
197
+ const compiledAggregator = aggregator({
198
+ beginTimestamp,
199
+ endTimestamp,
200
+ filterExpression,
201
+ aggregationExpression,
202
+ rawAggregations,
203
+ maxRows,
204
+ totals,
205
+ subtotalsForOuterGrouper,
206
+ parentGroupTotals,
207
+ });
208
+ if (!beginStock)
209
+ transactions.filter((t) => t.type != '0').forEach(compiledAggregator);
210
+ else
211
+ transactions.forEach(compiledAggregator);
212
+ return rawAggregations;
213
+ }
214
+ function whichShardsToProcess(dateRange, granularity) {
215
+ const padl = function (s) {
216
+ if (s > 9)
217
+ return '' + s;
218
+ return '0' + s;
219
+ };
220
+ const result = [];
221
+ if (granularity == 'year')
222
+ for (let year = dateRange.fromYear; year <= dateRange.toYear; year++)
223
+ result.push(year);
224
+ else
225
+ for (let year = dateRange.fromYear; year <= dateRange.toYear; year++) {
226
+ if (year == dateRange.fromYear && year < dateRange.toYear)
227
+ for (let month = dateRange.fromMonth; month <= 12; month++)
228
+ result.push('' + year + '' + padl(month));
229
+ if (year == dateRange.fromYear && year == dateRange.toYear)
230
+ for (let month = dateRange.fromMonth; month <= dateRange.toMonth; month++)
231
+ result.push('' + year + '' + padl(month));
232
+ if (year != dateRange.fromYear && year != dateRange.toYear)
233
+ for (let month = 1; month <= 12; month++)
234
+ result.push('' + year + '' + padl(month));
235
+ if (year != dateRange.fromYear && year == dateRange.toYear)
236
+ for (let month = 1; month <= dateRange.toMonth; month++)
237
+ result.push('' + year + '' + padl(month));
238
+ }
239
+ return result;
240
+ }
241
+ /**
242
+ *
243
+ * @param {*} v: a vector
244
+ * @param {*} timeFrame: nr miliseconds in timeframe
245
+ *
246
+ * Calculate all derived columns on a given vector.
247
+ */
248
+ function postAgg(v, timeFrame) {
249
+ v[QTY_END_STOCK] = v[QTY_STOCK] + v[QTY_RECIEVED] - v[QTY_SOLD] + v[QTY_CHANGE] + v[QTY_TRANSIT];
250
+ v[AMOUNT_END_STOCK] = v[AMOUNT_STOCK] + v[AMOUNT_RECIEVED] - v[COSTPRICE_SOLD] + v[AMOUNT_CHANGE] + v[AMOUNT_TRANSIT] + v[AMOUNT_REVALUATE];
251
+ v[QTY_END_SHELF_STOCK] = v[QTY_SHELF_STOCK] + v[QTY_RECIEVED] - v[QTY_SOLD] - v[QTY_CONSIGNMENT] + v[QTY_CHANGE] + v[QTY_TRANSIT];
252
+ v[AMOUNT_END_SHELF_STOCK] = v[AMOUNT_SHELF_STOCK] + v[AMOUNT_RECIEVED] - v[COSTPRICE_SOLD] + v[COSTPRICE_CONSIGNMENT] + v[AMOUNT_CHANGE] + v[AMOUNT_TRANSIT];
253
+ v[SELLOUT_PERCENTAGE] = (v[QTY_SOLD] / (v[QTY_SOLD] + v[QTY_END_STOCK])) * 100;
254
+ v[ROI] = v[AMOUNT_SOLD_EXCL] - v[AMOUNT_RECIEVED] + v[AMOUNT_TRANSIT];
255
+ v[QTY_AVG_STOCK] = (v[QTY_STOCK] * timeFrame + v[STOCK_TIME_PRODUCT]) / timeFrame;
256
+ v[VALUE_AVG_STOCK] = (v[AMOUNT_STOCK] * timeFrame + v[STOCKAMOUNT_TIME_PRODUCT]) / timeFrame;
257
+ v[QTY_TURNOVER_VELOCITY] = ((v[QTY_SOLD] / v[QTY_AVG_STOCK]) * msYear) / timeFrame;
258
+ v[AMOUNT_TURNOVER_VELOCITY] = ((v[COSTPRICE_SOLD] / v[VALUE_AVG_STOCK]) * msYear) / timeFrame;
259
+ v[PROFIT] = v[AMOUNT_SOLD_EXCL] - v[COSTPRICE_SOLD];
260
+ v[PROFITABILITY] = (v[PROFIT] / v[COSTPRICE_SOLD]) * v[QTY_TURNOVER_VELOCITY] * 100;
261
+ v[MARGIN] = (v[PROFIT] / v[AMOUNT_SOLD_EXCL]) * 100;
262
+ v[RETURN_PERCENTAGE] = (v[QTY_RETURN] / v[QTY_RECIEVED]) * 100;
263
+ v[QTY_SOLD_BEFORE_RETURNS] = v[QTY_SOLD] + Math.abs(v[QTY_RETURN]);
264
+ }
265
+ function getTimeGrouper(groupers) {
266
+ if (groupers.includes('transaction.year'))
267
+ return types_1.TimeGranularityE.YEAR;
268
+ if (groupers.includes('transaction.month'))
269
+ return types_1.TimeGranularityE.MONTH;
270
+ if (groupers.includes('transaction.quarter'))
271
+ return types_1.TimeGranularityE.QUARTER;
272
+ if (groupers.includes('transaction.week'))
273
+ return types_1.TimeGranularityE.WEEK;
274
+ if (groupers.includes('transaction.day'))
275
+ return types_1.TimeGranularityE.DAY;
276
+ return;
277
+ }
278
+ function runQuery(timeframe, rawAggregations, beginStock, transactions, selectedGroupsValues, skus, warehouses, subtotalsForOuterGrouper, computedFilter, parentGroupTotals) {
279
+ const timeGrouper = getTimeGrouper(selectedGroupsValues);
280
+ const aggregationExpression = selectedGroupsValues
281
+ .map((g) => {
282
+ if (g == 'transaction.year')
283
+ return '"' + timeframe.hrYear + '"';
284
+ if (g == 'transaction.quarter')
285
+ return '"' + timeframe.hrQuarter + '"';
286
+ if (g == 'transaction.month')
287
+ return '"' + timeframe.hrMonth + '"';
288
+ if (g == 'transaction.week')
289
+ return '"' + timeframe.hrWeek + '"';
290
+ if (g == 'transaction.day')
291
+ return '"' + timeframe.hrDay + '"';
292
+ return g;
293
+ })
294
+ .join('+"\t"+');
295
+ try {
296
+ aggregate({
297
+ beginTimestamp: timeframe.beginTimeStamp,
298
+ endTimestamp: timeframe.endTimeStamp,
299
+ filterExpression: computedFilter,
300
+ aggregationExpression: aggregationExpression,
301
+ transactions: transactions,
302
+ beginStock: beginStock,
303
+ relations: {
304
+ sku: skus,
305
+ wh: warehouses,
306
+ },
307
+ rawAggregations: rawAggregations,
308
+ maxRows: 100000,
309
+ totals: timeGrouper === undefined,
310
+ subtotalsForOuterGrouper,
311
+ parentGroupTotals,
312
+ });
313
+ }
314
+ catch (e) {
315
+ return rawAggregations; // most probably an end-user typo in the manualFilter
316
+ }
317
+ return rawAggregations;
318
+ }
319
+ const getDateRangeFromPicker = function (inputDates) {
320
+ if (!Array.isArray(inputDates))
321
+ return {
322
+ beginTimeStamp: 0,
323
+ endTimeStamp: 0,
324
+ timeFrame: 0,
325
+ fromYear: 0,
326
+ toYear: 0,
327
+ fromMonth: 0,
328
+ toMonth: 0,
329
+ hrYear: '',
330
+ hrQuarter: '',
331
+ hrMonth: '',
332
+ hrWeek: '',
333
+ hrDay: '',
334
+ };
335
+ const dates = inputDates.slice();
336
+ dates.sort((a, b) => {
337
+ return ('' + a).localeCompare(b);
338
+ });
339
+ const fromDate = (0, date_fns_1.parse)(dates[0], 'yyyy-MM-dd', new Date());
340
+ fromDate.setSeconds(fromDate.getSeconds());
341
+ const toDate = (0, date_fns_1.parse)(dates[1], 'yyyy-MM-dd', new Date());
342
+ const beginTimeStamp = fromDate.getTime();
343
+ const endTimeStamp = toDate.getTime() + 24 * 3600 * 1000;
344
+ const timeFrame = endTimeStamp - beginTimeStamp;
345
+ return {
346
+ beginTimeStamp,
347
+ endTimeStamp,
348
+ timeFrame,
349
+ fromYear: parseInt((0, date_fns_1.format)(fromDate, 'yyyy')),
350
+ toYear: parseInt((0, date_fns_1.format)(toDate, 'yyyy')),
351
+ fromMonth: parseInt((0, date_fns_1.format)(fromDate, 'MM')),
352
+ toMonth: parseInt((0, date_fns_1.format)(toDate, 'MM')),
353
+ hrYear: (0, date_fns_1.format)(fromDate, 'yyyy'),
354
+ hrQuarter: (0, date_fns_1.format)(fromDate, 'yyyy-QQQ'),
355
+ hrMonth: (0, date_fns_1.format)(fromDate, 'yyyy-MM'),
356
+ hrWeek: (0, date_fns_1.format)(fromDate, 'RRRR-II'),
357
+ hrDay: (0, date_fns_1.format)(fromDate, 'yyyy-MM-dd'),
358
+ };
359
+ };
360
+ exports.default = {
361
+ getDateRangeFromPicker,
362
+ getTimeGrouper,
363
+ filtrexOptions,
364
+ postAgg,
365
+ runQuery,
366
+ whichShardsToProcess,
367
+ AMOUNT_CHANGE,
368
+ AMOUNT_CONSIGNMENT,
369
+ AMOUNT_END_SHELF_STOCK,
370
+ AMOUNT_END_STOCK,
371
+ AMOUNT_MINIMUM_STOCK,
372
+ AMOUNT_PO,
373
+ AMOUNT_PO_COMPLETE,
374
+ AMOUNT_RECIEVED,
375
+ AMOUNT_RETURN,
376
+ AMOUNT_REVALUATE,
377
+ AMOUNT_SHELF_STOCK,
378
+ AMOUNT_SOLD,
379
+ AMOUNT_SOLD_DISCOUNT,
380
+ AMOUNT_SOLD_EXCL,
381
+ AMOUNT_SOLD_MAX,
382
+ AMOUNT_STOCK,
383
+ AMOUNT_TRANSIT,
384
+ AMOUNT_TURNOVER_VELOCITY,
385
+ COSTPRICE_CONSIGNMENT,
386
+ COSTPRICE_SOLD,
387
+ MARGIN,
388
+ MAX_RECIEVE_TIMESTAMP,
389
+ PROFIT,
390
+ PROFITABILITY,
391
+ QTY_AVG_STOCK,
392
+ QTY_CHANGE,
393
+ QTY_CONSIGNMENT,
394
+ QTY_END_SHELF_STOCK,
395
+ QTY_END_STOCK,
396
+ QTY_MINIMUM_STOCK,
397
+ QTY_PO,
398
+ QTY_PO_COMPLETE,
399
+ QTY_RECIEVED,
400
+ QTY_RETURN,
401
+ QTY_SHELF_STOCK,
402
+ QTY_SOLD,
403
+ QTY_SOLD_BEFORE_RETURNS,
404
+ QTY_STOCK,
405
+ QTY_TRANSACTION,
406
+ QTY_TRANSIT,
407
+ QTY_TURNOVER_VELOCITY,
408
+ RETURN_PERCENTAGE,
409
+ ROI,
410
+ SELLOUT_PERCENTAGE,
411
+ STOCKAMOUNT_TIME_PRODUCT,
412
+ STOCK_TIME_PRODUCT,
413
+ VALUE_AVG_STOCK,
414
+ };
package/dist/index.d.ts CHANGED
@@ -9,5 +9,6 @@ export { default as sizeToMap } from './sizeToMap';
9
9
  export { default as findSkuByBarcode } from './findSkuByBarcode';
10
10
  export { default as round2 } from './round2';
11
11
  export { default as transaction } from './transaction';
12
+ export { default as articleStatus } from './articleStatus';
12
13
  export * from './types';
13
14
  export * from './consts';
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.transaction = exports.round2 = exports.findSkuByBarcode = exports.sizeToMap = exports.hasOnlyDigits = exports.hashBrand = exports.getPreferedPropertyMappings = exports.isean13 = exports.ean13 = exports.deepCopy = exports.buildPropertyMappingFn = void 0;
20
+ exports.articleStatus = exports.transaction = exports.round2 = exports.findSkuByBarcode = exports.sizeToMap = exports.hasOnlyDigits = exports.hashBrand = exports.getPreferedPropertyMappings = exports.isean13 = exports.ean13 = exports.deepCopy = exports.buildPropertyMappingFn = void 0;
21
21
  var buildPropertyMappingFn_1 = require("./buildPropertyMappingFn");
22
22
  Object.defineProperty(exports, "buildPropertyMappingFn", { enumerable: true, get: function () { return __importDefault(buildPropertyMappingFn_1).default; } });
23
23
  var deepCopy_1 = require("./deepCopy");
@@ -40,5 +40,7 @@ var round2_1 = require("./round2");
40
40
  Object.defineProperty(exports, "round2", { enumerable: true, get: function () { return __importDefault(round2_1).default; } });
41
41
  var transaction_1 = require("./transaction");
42
42
  Object.defineProperty(exports, "transaction", { enumerable: true, get: function () { return __importDefault(transaction_1).default; } });
43
+ var articleStatus_1 = require("./articleStatus");
44
+ Object.defineProperty(exports, "articleStatus", { enumerable: true, get: function () { return __importDefault(articleStatus_1).default; } });
43
45
  __exportStar(require("./types"), exports);
44
46
  __exportStar(require("./consts"), exports);
package/dist/types.d.ts CHANGED
@@ -256,6 +256,8 @@ interface dbTransactionI {
256
256
  docnr: string;
257
257
  time: number;
258
258
  vector: number[];
259
+ row?: number;
260
+ unixtime?: number;
259
261
  }
260
262
  interface TransactionI {
261
263
  type: transactionTypeE;
@@ -269,5 +271,54 @@ interface TransactionI {
269
271
  sellprice: number;
270
272
  vat?: number;
271
273
  buyprice: number;
274
+ vector: Float64Array;
272
275
  }
273
- export { RowI, StockTransferSelectionI, MarkedSkuI, vatCategoryE, tagTypeE, SkuI, GroupI, ProductI, ImageI, AttributeI, ColorI, MatrixI, StockTransferDataI, StockTransferMatrixI, BrandSettingI, mappingStrategyE, subscriptionE, dbTransactionI, TransactionI, transactionTypeE, };
276
+ type RaGI = Record<string, Float64Array>;
277
+ interface AggregateFnI {
278
+ beginTimestamp: number;
279
+ endTimestamp: number;
280
+ filterExpression: string;
281
+ aggregationExpression: string;
282
+ transactions: TransactionI[];
283
+ beginStock: boolean;
284
+ relations: any;
285
+ rawAggregations: RaGI;
286
+ maxRows: number;
287
+ totals: boolean;
288
+ subtotalsForOuterGrouper?: boolean;
289
+ parentGroupTotals?: boolean;
290
+ }
291
+ interface AggregatorFnI {
292
+ beginTimestamp: number;
293
+ endTimestamp: number;
294
+ filterExpression: string;
295
+ aggregationExpression: string;
296
+ rawAggregations: RaGI;
297
+ maxRows: number;
298
+ totals: boolean;
299
+ subtotalsForOuterGrouper?: boolean;
300
+ reportViz?: string;
301
+ parentGroupTotals?: boolean;
302
+ }
303
+ interface HrTimeframeI {
304
+ beginTimeStamp: number;
305
+ endTimeStamp: number;
306
+ timeFrame: number;
307
+ fromYear: number;
308
+ toYear: number;
309
+ fromMonth: number;
310
+ toMonth: number;
311
+ hrYear: string;
312
+ hrQuarter: string;
313
+ hrMonth: string;
314
+ hrWeek: string;
315
+ hrDay: string;
316
+ }
317
+ declare enum TimeGranularityE {
318
+ DAY = "day",
319
+ WEEK = "week",
320
+ MONTH = "month",
321
+ QUARTER = "quarter",
322
+ YEAR = "year"
323
+ }
324
+ export { RowI, StockTransferSelectionI, MarkedSkuI, vatCategoryE, tagTypeE, SkuI, GroupI, ProductI, ImageI, AttributeI, ColorI, MatrixI, StockTransferDataI, StockTransferMatrixI, BrandSettingI, mappingStrategyE, subscriptionE, dbTransactionI, TransactionI, transactionTypeE, HrTimeframeI, TimeGranularityE, AggregatorFnI, AggregateFnI, RaGI, };
package/dist/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.transactionTypeE = exports.subscriptionE = exports.mappingStrategyE = exports.tagTypeE = exports.vatCategoryE = void 0;
3
+ exports.TimeGranularityE = exports.transactionTypeE = exports.subscriptionE = exports.mappingStrategyE = exports.tagTypeE = exports.vatCategoryE = void 0;
4
4
  var subscriptionE;
5
5
  (function (subscriptionE) {
6
6
  subscriptionE["DATA_ONLY"] = "data_only";
@@ -54,3 +54,12 @@ var transactionTypeE;
54
54
  transactionTypeE["OFFER"] = "98";
55
55
  })(transactionTypeE || (transactionTypeE = {}));
56
56
  exports.transactionTypeE = transactionTypeE;
57
+ var TimeGranularityE;
58
+ (function (TimeGranularityE) {
59
+ TimeGranularityE["DAY"] = "day";
60
+ TimeGranularityE["WEEK"] = "week";
61
+ TimeGranularityE["MONTH"] = "month";
62
+ TimeGranularityE["QUARTER"] = "quarter";
63
+ TimeGranularityE["YEAR"] = "year";
64
+ })(TimeGranularityE || (TimeGranularityE = {}));
65
+ exports.TimeGranularityE = TimeGranularityE;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwear/latestcollectioncore",
3
- "version": "1.0.76",
3
+ "version": "1.0.78",
4
4
  "description": "Core functions for LatestCollections applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,5 +26,9 @@
26
26
  "mocha": "^10.2.0",
27
27
  "ts-node": "^10.9.1",
28
28
  "typescript": "^4.9.4"
29
+ },
30
+ "dependencies": {
31
+ "date-fns": "^3.0.0",
32
+ "filtrex": "^3.0.0"
29
33
  }
30
34
  }
@@ -0,0 +1,464 @@
1
+ import { compileExpression } from 'filtrex'
2
+ import { TransactionI, RaGI, AggregateFnI, AggregatorFnI, HrTimeframeI, TimeGranularityE, SkuI } from './types'
3
+ import transaction from './transaction'
4
+ import { format, parse } from 'date-fns'
5
+
6
+ // fields in Vector
7
+ const {
8
+ QTY_TRANSACTION,
9
+ QTY_STOCK,
10
+ AMOUNT_STOCK,
11
+ QTY_SHELF_STOCK,
12
+ AMOUNT_SHELF_STOCK,
13
+ AMOUNT_REVALUATE,
14
+ QTY_RECIEVED,
15
+ AMOUNT_RECIEVED,
16
+ QTY_SOLD,
17
+ AMOUNT_SOLD,
18
+ AMOUNT_SOLD_EXCL,
19
+ COSTPRICE_SOLD,
20
+ QTY_CHANGE,
21
+ AMOUNT_CHANGE,
22
+ QTY_TRANSIT,
23
+ AMOUNT_TRANSIT,
24
+ QTY_PO,
25
+ AMOUNT_PO,
26
+ QTY_PO_COMPLETE,
27
+ AMOUNT_PO_COMPLETE,
28
+ QTY_MINIMUM_STOCK,
29
+ AMOUNT_MINIMUM_STOCK,
30
+ QTY_CONSIGNMENT,
31
+ AMOUNT_CONSIGNMENT,
32
+ COSTPRICE_CONSIGNMENT,
33
+ } = transaction.transactionVector
34
+
35
+ // DERIVED FIELDS
36
+ const AMOUNT_SOLD_MAX = 25
37
+ const AMOUNT_SOLD_DISCOUNT = 26
38
+ const MAX_RECIEVE_TIMESTAMP = 27
39
+ const STOCK_TIME_PRODUCT = 28
40
+ const STOCKAMOUNT_TIME_PRODUCT = 29
41
+ const QTY_RETURN = 30
42
+ const AMOUNT_RETURN = 31
43
+
44
+ // Vector length to allocate for aggregation
45
+ const VECTOR_LENGTH = 32
46
+
47
+ // POST AGGREGATION COLUMNS
48
+ const SELLOUT_PERCENTAGE = 32
49
+ const ROI = 33
50
+ const QTY_AVG_STOCK = 34
51
+ const VALUE_AVG_STOCK = 35
52
+ const QTY_TURNOVER_VELOCITY = 36
53
+ const AMOUNT_TURNOVER_VELOCITY = 37
54
+ const PROFITABILITY = 38
55
+ const MARGIN = 39
56
+ const PROFIT = 40
57
+ const QTY_END_SHELF_STOCK = 41
58
+ const AMOUNT_END_SHELF_STOCK = 42
59
+ const QTY_END_STOCK = 43
60
+ const AMOUNT_END_STOCK = 44
61
+ const RETURN_PERCENTAGE = 45
62
+ const QTY_SOLD_BEFORE_RETURNS = 46
63
+
64
+ const BEGIN_STOCK_VECTOR_START = QTY_STOCK
65
+ const BEGIN_STOCK_VECTOR_END = AMOUNT_SHELF_STOCK + 1
66
+
67
+ const msYear = 24 * 3600 * 365 * 1000
68
+ const globalJoinableTables = { sku: 'ean', wh: 'wh', transaction: 'row' } // defines the keys we can join on
69
+
70
+ /**
71
+ * Global variables
72
+ */
73
+ let globalRelations = {} // holds the joined tables
74
+ let globalNrAggegationExpressions = 0 // tracks the # aggregations rows so we can quit computing if it grows out of bounds
75
+
76
+ /**
77
+ * The propFunction for FiltreX needs some data that can't be passed as parameters. That's why the 'globalsRelations' variable is needed.
78
+ * TODO: Functional: Experiment with curried functions
79
+ */
80
+ function propFunction(propertyName: string, getPropertyName: Function, obj: Record<string, any>): any {
81
+ const positionOfDot = propertyName.indexOf('.')
82
+ if (positionOfDot < 0) return getPropertyName(propertyName)
83
+ const table = propertyName.substring(0, positionOfDot)
84
+ const field = propertyName.substring(positionOfDot + 1)
85
+ if (table == 'transaction') return getPropertyName(field)
86
+ return globalRelations[table]?.[obj[globalJoinableTables[table]]]?.[field] || '~ ?'
87
+ }
88
+
89
+ const lower = (s: string) => s.toLowerCase()
90
+ const upper = (s: string) => s.toUpperCase()
91
+
92
+ const filtrexOptions = {
93
+ extraFunctions: { lower, upper },
94
+ customProp: propFunction,
95
+ }
96
+
97
+ // This is how the actual joining of 2 vectors happens
98
+ function addVectors(targetVector: Float64Array, otherVector: Float64Array, begin: number, end: number): void {
99
+ for (let i = begin; i < end; i++) targetVector[i] += otherVector[i] || 0
100
+ }
101
+
102
+ // This function takes a transaction and adds it to the aggregations
103
+ function addTransactionToAggregations(beginTimestamp: number, endTimestamp: number, aggregateKey: string, rawAggregations: RaGI, transaction: TransactionI, maxRows: number): void {
104
+ let aggregateVector = rawAggregations[aggregateKey]
105
+ if (globalNrAggegationExpressions >= maxRows) return
106
+ if (!aggregateVector) {
107
+ globalNrAggegationExpressions++
108
+ aggregateVector = new Float64Array(VECTOR_LENGTH)
109
+ rawAggregations[aggregateKey] = aggregateVector
110
+ }
111
+ const vector = transaction.vector
112
+ if (transaction.type == '1' && transaction.time > aggregateVector[MAX_RECIEVE_TIMESTAMP]) aggregateVector[MAX_RECIEVE_TIMESTAMP] = transaction.time
113
+ if (transaction.time <= beginTimestamp) return addVectors(aggregateVector, vector, BEGIN_STOCK_VECTOR_START, BEGIN_STOCK_VECTOR_END)
114
+ if (transaction.time > endTimestamp) return
115
+ addVectors(aggregateVector, vector, BEGIN_STOCK_VECTOR_END, vector.length)
116
+
117
+ // Calculate derived fields
118
+
119
+ aggregateVector[QTY_RETURN] += vector[QTY_SOLD] < 0 ? vector[QTY_SOLD] : 0
120
+ aggregateVector[AMOUNT_RETURN] += vector[AMOUNT_SOLD_EXCL] < 0 ? vector[AMOUNT_SOLD_EXCL] : 0
121
+
122
+ aggregateVector[STOCK_TIME_PRODUCT] +=
123
+ (vector[QTY_RECIEVED] || 0 + vector[QTY_TRANSIT] || 0 + vector[QTY_CHANGE] || 0 - vector[QTY_SOLD] || 0) * (endTimestamp - transaction.time)
124
+ aggregateVector[STOCKAMOUNT_TIME_PRODUCT] +=
125
+ (vector[AMOUNT_RECIEVED] || 0 + vector[AMOUNT_TRANSIT] || 0 + vector[AMOUNT_CHANGE] || 0 - vector[COSTPRICE_SOLD] || 0) * (endTimestamp - transaction.time)
126
+ }
127
+
128
+ function buildAggregationEvaluator(aggregationExpression: string): Function {
129
+ return compileExpression(aggregationExpression, filtrexOptions)
130
+ }
131
+
132
+ function prepareSubTotalAggKey(aggKey) {
133
+ return aggKey.split('\t').slice(0, -1).join('\t').concat(' Σ')
134
+ }
135
+
136
+ /*
137
+ * A factory function that returns a function that can be used to aggregate transactions
138
+ * The factory function is called once for each aggregationExpression
139
+ * @param {Object} options
140
+ *
141
+ * @param {number} options.beginTimestamp - The begin of the time frame UNIX timestamp
142
+ * @param {number} options.endTimestamp - The end of the time frame UNIX timestamp
143
+ *
144
+ * @param {string} options.filterExpression - The FiltreX preprocessed filter expression for: brand, collection, warehouse and articleGroup
145
+ * Example: sku.brand in ("Gabor") and sku.collection not in ("21summer")
146
+ *
147
+ * @param {string} options.aggregationExpression - The aggregation expression that contains the aggregateKey
148
+ * Example: sku.brand+" "+sku.collection+" "+sku.articleGroup+" "+sku.articleCodeSupplier
149
+ *
150
+ * @param {Object} options.rawAggregations - The raw aggregations object where keys are the aggregateKeys and values are the Float64Array vectors
151
+ *
152
+ * @param {number} options.maxRows - The maximum number of rows to aggregate
153
+ * @param {boolean} options.totals - Whether to aggregate totals
154
+ * @param {boolean} options.subtotalsForOuterGrouper - Whether to aggregate single subtotals
155
+ *
156
+ * @returns {Function} - The aggregator function
157
+ */
158
+ function aggregator({
159
+ beginTimestamp,
160
+ endTimestamp,
161
+ filterExpression,
162
+ aggregationExpression,
163
+ rawAggregations,
164
+ maxRows,
165
+ totals,
166
+ subtotalsForOuterGrouper,
167
+ parentGroupTotals,
168
+ }: AggregatorFnI) {
169
+ const filterExpressionCache = {}
170
+
171
+ let filter
172
+ let filterDependencyEvaluator
173
+ if (filterExpression) {
174
+ filter = compileExpression(filterExpression, filtrexOptions)
175
+ // Maintain a cache of evaluated filterExpressions
176
+ // The granularity of the cache is determined by the joined tables in the filterExpression
177
+ const filterGranularity = Object.entries(globalJoinableTables)
178
+ .filter((join) => filterExpression.includes(join[0] + '.'))
179
+ .map((join) => join[1])
180
+ filterDependencyEvaluator = compileExpression(filterGranularity.join('+'))
181
+ }
182
+ const aggregationEvaluator = buildAggregationEvaluator(aggregationExpression)
183
+ // Maintain a cache of evaluated aggregationExpressions
184
+ // The granularity of the cache is determined by the joined tables in the aggregationExpression
185
+ const aggregationExpressionCache = {}
186
+ const aggregationGranularity = Object.entries(globalJoinableTables)
187
+ .filter((join) => aggregationExpression.includes(join[0] + '.'))
188
+ .map((join) => join[1])
189
+
190
+ let aggregationDependencyExpression = aggregationGranularity.join('+')
191
+ if (!aggregationDependencyExpression) aggregationDependencyExpression = '"N.A."'
192
+ const aggregationDependencyEvaluator = compileExpression(aggregationDependencyExpression)
193
+ return function (transaction: TransactionI): void {
194
+ if (filter) {
195
+ const filterDependency = filterDependencyEvaluator(transaction)
196
+ const filterExpression = filterExpressionCache[filterDependency]
197
+ if (filterExpression == -1) return
198
+ if (!filterExpression) {
199
+ filterExpressionCache[filterDependency] = filter(transaction) ? 1 : -1
200
+ if (filterExpressionCache[filterDependency] == -1) return
201
+ }
202
+ }
203
+ const aggregationDependency = aggregationDependencyEvaluator(transaction)
204
+ let aggregateKey = aggregationExpressionCache[aggregationDependency]
205
+ if (!aggregateKey) {
206
+ aggregateKey = aggregationEvaluator(transaction)
207
+ aggregationExpressionCache[aggregationDependency] = aggregateKey
208
+ }
209
+ // Prepare joined values for derived fields
210
+ if (transaction.type == '2') {
211
+ const sku = globalRelations['sku'][transaction.ean]
212
+ const vector = transaction.vector
213
+ vector[AMOUNT_SOLD_MAX] = (sku?.price || 0) * vector[QTY_SOLD]
214
+ vector[AMOUNT_SOLD_DISCOUNT] = vector[AMOUNT_SOLD_MAX] - vector[AMOUNT_SOLD]
215
+ }
216
+
217
+ if (totals) addTransactionToAggregations(beginTimestamp, endTimestamp, '** TOTAL **', rawAggregations, transaction, maxRows)
218
+
219
+ // Single subtotals are subtotals for first grouper level
220
+ // Example: for 4 groupers we have rows like
221
+ // g0 g1 g2 g3 <- normal row with no subtotal
222
+ // g0 <- subtotal for g0
223
+ if (subtotalsForOuterGrouper == true) {
224
+ addTransactionToAggregations(beginTimestamp, endTimestamp, `${aggregateKey.substr(0, aggregateKey.indexOf('\t'))} Σ`, rawAggregations, transaction, maxRows)
225
+ }
226
+
227
+ if (parentGroupTotals) {
228
+ const KPIKey = prepareSubTotalAggKey(aggregateKey)
229
+ addTransactionToAggregations(beginTimestamp, endTimestamp, KPIKey, rawAggregations, transaction, maxRows)
230
+ } else {
231
+ addTransactionToAggregations(beginTimestamp, endTimestamp, aggregateKey, rawAggregations, transaction, maxRows)
232
+ }
233
+ }
234
+ }
235
+
236
+ function aggregate({
237
+ beginTimestamp,
238
+ endTimestamp,
239
+ filterExpression,
240
+ aggregationExpression,
241
+ transactions,
242
+ beginStock,
243
+ relations,
244
+ rawAggregations,
245
+ maxRows,
246
+ totals,
247
+ subtotalsForOuterGrouper,
248
+ parentGroupTotals,
249
+ }: AggregateFnI) {
250
+ globalNrAggegationExpressions = 0
251
+ globalRelations = relations
252
+
253
+ const compiledAggregator = aggregator({
254
+ beginTimestamp,
255
+ endTimestamp,
256
+ filterExpression,
257
+ aggregationExpression,
258
+ rawAggregations,
259
+ maxRows,
260
+ totals,
261
+ subtotalsForOuterGrouper,
262
+ parentGroupTotals,
263
+ })
264
+ if (!beginStock) transactions.filter((t: TransactionI) => t.type != '0').forEach(compiledAggregator)
265
+ else transactions.forEach(compiledAggregator)
266
+
267
+ return rawAggregations
268
+ }
269
+ function whichShardsToProcess(dateRange: any, granularity: 'year' | 'month'): any[] {
270
+ const padl = function (s: number) {
271
+ if (s > 9) return '' + s
272
+ return '0' + s
273
+ }
274
+ const result = [] as any[]
275
+ if (granularity == 'year') for (let year = dateRange.fromYear; year <= dateRange.toYear; year++) result.push(year)
276
+ else
277
+ for (let year = dateRange.fromYear; year <= dateRange.toYear; year++) {
278
+ if (year == dateRange.fromYear && year < dateRange.toYear) for (let month = dateRange.fromMonth; month <= 12; month++) result.push('' + year + '' + padl(month))
279
+ if (year == dateRange.fromYear && year == dateRange.toYear)
280
+ for (let month = dateRange.fromMonth; month <= dateRange.toMonth; month++) result.push('' + year + '' + padl(month))
281
+ if (year != dateRange.fromYear && year != dateRange.toYear) for (let month = 1; month <= 12; month++) result.push('' + year + '' + padl(month))
282
+ if (year != dateRange.fromYear && year == dateRange.toYear) for (let month = 1; month <= dateRange.toMonth; month++) result.push('' + year + '' + padl(month))
283
+ }
284
+ return result
285
+ }
286
+ /**
287
+ *
288
+ * @param {*} v: a vector
289
+ * @param {*} timeFrame: nr miliseconds in timeframe
290
+ *
291
+ * Calculate all derived columns on a given vector.
292
+ */
293
+ function postAgg(v: any, timeFrame: number): void {
294
+ v[QTY_END_STOCK] = v[QTY_STOCK] + v[QTY_RECIEVED] - v[QTY_SOLD] + v[QTY_CHANGE] + v[QTY_TRANSIT]
295
+ v[AMOUNT_END_STOCK] = v[AMOUNT_STOCK] + v[AMOUNT_RECIEVED] - v[COSTPRICE_SOLD] + v[AMOUNT_CHANGE] + v[AMOUNT_TRANSIT] + v[AMOUNT_REVALUATE]
296
+ v[QTY_END_SHELF_STOCK] = v[QTY_SHELF_STOCK] + v[QTY_RECIEVED] - v[QTY_SOLD] - v[QTY_CONSIGNMENT] + v[QTY_CHANGE] + v[QTY_TRANSIT]
297
+ v[AMOUNT_END_SHELF_STOCK] = v[AMOUNT_SHELF_STOCK] + v[AMOUNT_RECIEVED] - v[COSTPRICE_SOLD] + v[COSTPRICE_CONSIGNMENT] + v[AMOUNT_CHANGE] + v[AMOUNT_TRANSIT]
298
+ v[SELLOUT_PERCENTAGE] = (v[QTY_SOLD] / (v[QTY_SOLD] + v[QTY_END_STOCK])) * 100
299
+ v[ROI] = v[AMOUNT_SOLD_EXCL] - v[AMOUNT_RECIEVED] + v[AMOUNT_TRANSIT]
300
+ v[QTY_AVG_STOCK] = (v[QTY_STOCK] * timeFrame + v[STOCK_TIME_PRODUCT]) / timeFrame
301
+ v[VALUE_AVG_STOCK] = (v[AMOUNT_STOCK] * timeFrame + v[STOCKAMOUNT_TIME_PRODUCT]) / timeFrame
302
+ v[QTY_TURNOVER_VELOCITY] = ((v[QTY_SOLD] / v[QTY_AVG_STOCK]) * msYear) / timeFrame
303
+ v[AMOUNT_TURNOVER_VELOCITY] = ((v[COSTPRICE_SOLD] / v[VALUE_AVG_STOCK]) * msYear) / timeFrame
304
+ v[PROFIT] = v[AMOUNT_SOLD_EXCL] - v[COSTPRICE_SOLD]
305
+ v[PROFITABILITY] = (v[PROFIT] / v[COSTPRICE_SOLD]) * v[QTY_TURNOVER_VELOCITY] * 100
306
+ v[MARGIN] = (v[PROFIT] / v[AMOUNT_SOLD_EXCL]) * 100
307
+ v[RETURN_PERCENTAGE] = (v[QTY_RETURN] / v[QTY_RECIEVED]) * 100
308
+ v[QTY_SOLD_BEFORE_RETURNS] = v[QTY_SOLD] + Math.abs(v[QTY_RETURN])
309
+ }
310
+
311
+ function getTimeGrouper(groupers: string[]): TimeGranularityE | undefined {
312
+ if (groupers.includes('transaction.year')) return TimeGranularityE.YEAR
313
+ if (groupers.includes('transaction.month')) return TimeGranularityE.MONTH
314
+ if (groupers.includes('transaction.quarter')) return TimeGranularityE.QUARTER
315
+ if (groupers.includes('transaction.week')) return TimeGranularityE.WEEK
316
+ if (groupers.includes('transaction.day')) return TimeGranularityE.DAY
317
+ return
318
+ }
319
+
320
+ function runQuery(
321
+ timeframe: HrTimeframeI,
322
+ rawAggregations: RaGI,
323
+ beginStock: boolean,
324
+ transactions: TransactionI[],
325
+ selectedGroupsValues: string[],
326
+ skus: SkuI[],
327
+ warehouses: any[],
328
+ subtotalsForOuterGrouper: boolean,
329
+ computedFilter: string,
330
+ parentGroupTotals: boolean
331
+ ) {
332
+ const timeGrouper = getTimeGrouper(selectedGroupsValues)
333
+ const aggregationExpression = selectedGroupsValues
334
+ .map((g) => {
335
+ if (g == 'transaction.year') return '"' + timeframe.hrYear + '"'
336
+ if (g == 'transaction.quarter') return '"' + timeframe.hrQuarter + '"'
337
+ if (g == 'transaction.month') return '"' + timeframe.hrMonth + '"'
338
+ if (g == 'transaction.week') return '"' + timeframe.hrWeek + '"'
339
+ if (g == 'transaction.day') return '"' + timeframe.hrDay + '"'
340
+ return g
341
+ })
342
+ .join('+"\t"+')
343
+ try {
344
+ aggregate({
345
+ beginTimestamp: timeframe.beginTimeStamp,
346
+ endTimestamp: timeframe.endTimeStamp,
347
+ filterExpression: computedFilter,
348
+ aggregationExpression: aggregationExpression,
349
+ transactions: transactions,
350
+ beginStock: beginStock,
351
+ relations: {
352
+ sku: skus,
353
+ wh: warehouses,
354
+ },
355
+ rawAggregations: rawAggregations,
356
+ maxRows: 100000,
357
+ totals: timeGrouper === undefined,
358
+ subtotalsForOuterGrouper,
359
+ parentGroupTotals,
360
+ })
361
+ } catch (e) {
362
+ return rawAggregations // most probably an end-user typo in the manualFilter
363
+ }
364
+ return rawAggregations
365
+ }
366
+
367
+ const getDateRangeFromPicker = function (inputDates: string[]): HrTimeframeI {
368
+ if (!Array.isArray(inputDates))
369
+ return {
370
+ beginTimeStamp: 0,
371
+ endTimeStamp: 0,
372
+ timeFrame: 0,
373
+ fromYear: 0,
374
+ toYear: 0,
375
+ fromMonth: 0,
376
+ toMonth: 0,
377
+ hrYear: '',
378
+ hrQuarter: '',
379
+ hrMonth: '',
380
+ hrWeek: '',
381
+ hrDay: '',
382
+ }
383
+ const dates = inputDates.slice()
384
+ dates.sort((a, b) => {
385
+ return ('' + a).localeCompare(b)
386
+ })
387
+ const fromDate = parse(dates[0], 'yyyy-MM-dd', new Date())
388
+ fromDate.setSeconds(fromDate.getSeconds())
389
+
390
+ const toDate = parse(dates[1], 'yyyy-MM-dd', new Date())
391
+ const beginTimeStamp = fromDate.getTime()
392
+ const endTimeStamp = toDate.getTime() + 24 * 3600 * 1000
393
+ const timeFrame = endTimeStamp - beginTimeStamp
394
+ return {
395
+ beginTimeStamp,
396
+ endTimeStamp,
397
+ timeFrame,
398
+ fromYear: parseInt(format(fromDate, 'yyyy')),
399
+ toYear: parseInt(format(toDate, 'yyyy')),
400
+ fromMonth: parseInt(format(fromDate, 'MM')),
401
+ toMonth: parseInt(format(toDate, 'MM')),
402
+ hrYear: format(fromDate, 'yyyy'),
403
+ hrQuarter: format(fromDate, 'yyyy-QQQ'),
404
+ hrMonth: format(fromDate, 'yyyy-MM'),
405
+ hrWeek: format(fromDate, 'RRRR-II'),
406
+ hrDay: format(fromDate, 'yyyy-MM-dd'),
407
+ }
408
+ }
409
+
410
+ export default {
411
+ getDateRangeFromPicker,
412
+ getTimeGrouper,
413
+ filtrexOptions,
414
+ postAgg,
415
+ runQuery,
416
+ whichShardsToProcess,
417
+ AMOUNT_CHANGE,
418
+ AMOUNT_CONSIGNMENT,
419
+ AMOUNT_END_SHELF_STOCK,
420
+ AMOUNT_END_STOCK,
421
+ AMOUNT_MINIMUM_STOCK,
422
+ AMOUNT_PO,
423
+ AMOUNT_PO_COMPLETE,
424
+ AMOUNT_RECIEVED,
425
+ AMOUNT_RETURN,
426
+ AMOUNT_REVALUATE,
427
+ AMOUNT_SHELF_STOCK,
428
+ AMOUNT_SOLD,
429
+ AMOUNT_SOLD_DISCOUNT,
430
+ AMOUNT_SOLD_EXCL,
431
+ AMOUNT_SOLD_MAX,
432
+ AMOUNT_STOCK,
433
+ AMOUNT_TRANSIT,
434
+ AMOUNT_TURNOVER_VELOCITY,
435
+ COSTPRICE_CONSIGNMENT,
436
+ COSTPRICE_SOLD,
437
+ MARGIN,
438
+ MAX_RECIEVE_TIMESTAMP,
439
+ PROFIT,
440
+ PROFITABILITY,
441
+ QTY_AVG_STOCK,
442
+ QTY_CHANGE,
443
+ QTY_CONSIGNMENT,
444
+ QTY_END_SHELF_STOCK,
445
+ QTY_END_STOCK,
446
+ QTY_MINIMUM_STOCK,
447
+ QTY_PO,
448
+ QTY_PO_COMPLETE,
449
+ QTY_RECIEVED,
450
+ QTY_RETURN,
451
+ QTY_SHELF_STOCK,
452
+ QTY_SOLD,
453
+ QTY_SOLD_BEFORE_RETURNS,
454
+ QTY_STOCK,
455
+ QTY_TRANSACTION,
456
+ QTY_TRANSIT,
457
+ QTY_TURNOVER_VELOCITY,
458
+ RETURN_PERCENTAGE,
459
+ ROI,
460
+ SELLOUT_PERCENTAGE,
461
+ STOCKAMOUNT_TIME_PRODUCT,
462
+ STOCK_TIME_PRODUCT,
463
+ VALUE_AVG_STOCK,
464
+ }
package/src/index.ts CHANGED
@@ -9,5 +9,6 @@ export { default as sizeToMap } from './sizeToMap'
9
9
  export { default as findSkuByBarcode } from './findSkuByBarcode'
10
10
  export { default as round2 } from './round2'
11
11
  export { default as transaction } from './transaction'
12
+ export { default as articleStatus } from './articleStatus'
12
13
  export * from './types'
13
14
  export * from './consts'
package/src/types.ts CHANGED
@@ -272,6 +272,8 @@ interface dbTransactionI {
272
272
  docnr: string
273
273
  time: number
274
274
  vector: number[]
275
+ row?: number
276
+ unixtime?: number
275
277
  }
276
278
  interface TransactionI {
277
279
  type: transactionTypeE
@@ -285,7 +287,61 @@ interface TransactionI {
285
287
  sellprice: number
286
288
  vat?: number
287
289
  buyprice: number
290
+ vector: Float64Array
288
291
  }
292
+
293
+ type RaGI = Record<string, Float64Array>
294
+
295
+ interface AggregateFnI {
296
+ beginTimestamp: number
297
+ endTimestamp: number
298
+ filterExpression: string
299
+ aggregationExpression: string
300
+ transactions: TransactionI[]
301
+ beginStock: boolean
302
+ relations: any
303
+ rawAggregations: RaGI
304
+ maxRows: number
305
+ totals: boolean
306
+ subtotalsForOuterGrouper?: boolean
307
+ parentGroupTotals?: boolean
308
+ }
309
+
310
+ interface AggregatorFnI {
311
+ beginTimestamp: number
312
+ endTimestamp: number
313
+ filterExpression: string
314
+ aggregationExpression: string
315
+ rawAggregations: RaGI
316
+ maxRows: number
317
+ totals: boolean
318
+ subtotalsForOuterGrouper?: boolean
319
+ reportViz?: string
320
+ parentGroupTotals?: boolean
321
+ }
322
+ interface HrTimeframeI {
323
+ beginTimeStamp: number
324
+ endTimeStamp: number
325
+ timeFrame: number
326
+ fromYear: number
327
+ toYear: number
328
+ fromMonth: number
329
+ toMonth: number
330
+ hrYear: string
331
+ hrQuarter: string
332
+ hrMonth: string
333
+ hrWeek: string
334
+ hrDay: string
335
+ }
336
+
337
+ enum TimeGranularityE {
338
+ DAY = 'day',
339
+ WEEK = 'week',
340
+ MONTH = 'month',
341
+ QUARTER = 'quarter',
342
+ YEAR = 'year',
343
+ }
344
+
289
345
  export {
290
346
  RowI,
291
347
  StockTransferSelectionI,
@@ -307,4 +363,9 @@ export {
307
363
  dbTransactionI,
308
364
  TransactionI,
309
365
  transactionTypeE,
366
+ HrTimeframeI,
367
+ TimeGranularityE,
368
+ AggregatorFnI,
369
+ AggregateFnI,
370
+ RaGI,
310
371
  }