@softwear/latestcollectioncore 1.0.205 → 1.0.207
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/articleStatus.js +295 -54
- package/dist/barcodeCanvas.d.ts +11 -0
- package/dist/barcodeCanvas.js +61 -0
- package/dist/pdf.d.ts +10 -2
- package/dist/reports.js +25 -2
- package/package.json +2 -1
- package/src/articleStatus.ts +294 -51
- package/src/barcodeCanvas.ts +59 -0
- package/src/pdf.ts +11 -2
- package/src/reports.ts +23 -1
- package/test/articleStatus.spec.js +81 -0
- package/test/reports.spec.ts +78 -0
package/dist/articleStatus.js
CHANGED
|
@@ -18,13 +18,13 @@ SELLOUT_PERCENTAGE, ROI, QTY_AVG_STOCK, VALUE_AVG_STOCK, QTY_TURNOVER_VELOCITY,
|
|
|
18
18
|
const VECTOR_LENGTH = 50;
|
|
19
19
|
const BEGIN_STOCK_VECTOR_START = QTY_STOCK;
|
|
20
20
|
const BEGIN_STOCK_VECTOR_END = AMOUNT_SHELF_STOCK + 1;
|
|
21
|
+
const TOTAL_KEY = '** TOTAL **';
|
|
21
22
|
const msYear = 24 * 3600 * 365 * 1000;
|
|
22
|
-
const globalJoinableTables = { customer: 'custnr', sku: 'ean', wh: 'wh'
|
|
23
|
+
const globalJoinableTables = { customer: 'custnr', sku: 'ean', wh: 'wh' }; // defines the keys we can join on
|
|
23
24
|
/**
|
|
24
25
|
* Global variables
|
|
25
26
|
*/
|
|
26
27
|
let globalRelations = {}; // holds the joined tables
|
|
27
|
-
let globalNrAggegationExpressions = 0; // tracks the # aggregations rows so we can quit computing if it grows out of bounds
|
|
28
28
|
/**
|
|
29
29
|
* The propFunction for FiltreX needs some data that can't be passed as parameters. That's why the 'globalsRelations' variable is needed.
|
|
30
30
|
* TODO: Functional: Experiment with curried functions
|
|
@@ -32,17 +32,28 @@ let globalNrAggegationExpressions = 0; // tracks the # aggregations rows so we c
|
|
|
32
32
|
function resolvePath(obj, path) {
|
|
33
33
|
return path.reduce((o, key) => o === null || o === void 0 ? void 0 : o[key], obj);
|
|
34
34
|
}
|
|
35
|
+
const MAX_PARSED_PROPERTIES = 1000;
|
|
36
|
+
const parsedProperties = new Map();
|
|
37
|
+
function parseProperty(propertyName) {
|
|
38
|
+
let parsed = parsedProperties.get(propertyName);
|
|
39
|
+
if (parsed)
|
|
40
|
+
return parsed;
|
|
41
|
+
const positionOfDot = propertyName.indexOf('.');
|
|
42
|
+
parsed = positionOfDot < 0 ? { table: undefined, path: [propertyName] } : { table: propertyName.substring(0, positionOfDot), path: propertyName.substring(positionOfDot + 1).split('.') };
|
|
43
|
+
if (parsedProperties.size >= MAX_PARSED_PROPERTIES)
|
|
44
|
+
parsedProperties.clear();
|
|
45
|
+
parsedProperties.set(propertyName, parsed);
|
|
46
|
+
return parsed;
|
|
47
|
+
}
|
|
35
48
|
function propFunction(propertyName, getPropertyName, obj) {
|
|
36
49
|
var _a;
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
50
|
+
const { table, path } = parseProperty(propertyName);
|
|
51
|
+
if (table === undefined)
|
|
39
52
|
return obj[propertyName];
|
|
40
|
-
const table = propertyName.substring(0, positionOfDot);
|
|
41
|
-
const path = propertyName.substring(positionOfDot + 1).split('.');
|
|
42
53
|
if (table == 'transaction')
|
|
43
|
-
return resolvePath(obj, path);
|
|
54
|
+
return path.length == 1 ? obj[path[0]] : resolvePath(obj, path);
|
|
44
55
|
const joinedRecord = (_a = globalRelations[table]) === null || _a === void 0 ? void 0 : _a[obj[globalJoinableTables[table]]];
|
|
45
|
-
return resolvePath(joinedRecord, path) || '~ ?';
|
|
56
|
+
return (path.length == 1 ? joinedRecord === null || joinedRecord === void 0 ? void 0 : joinedRecord[path[0]] : resolvePath(joinedRecord, path)) || '~ ?';
|
|
46
57
|
}
|
|
47
58
|
const lower = (s) => s.toLowerCase();
|
|
48
59
|
const upper = (s) => s.toUpperCase();
|
|
@@ -50,23 +61,50 @@ const filtrexOptions = {
|
|
|
50
61
|
extraFunctions: { lower, upper },
|
|
51
62
|
customProp: propFunction,
|
|
52
63
|
};
|
|
53
|
-
|
|
64
|
+
const filtrexKeywords = ['and', 'or', 'not', 'in', 'of', 'if', 'then', 'else', 'mod'];
|
|
65
|
+
/**
|
|
66
|
+
* The transaction fields an expression result depends on. Two transactions with the same values for
|
|
67
|
+
* these fields give the same result, so the expression is evaluated once per combination.
|
|
68
|
+
* - A joined table (sku., wh., customer.) depends on its join key.
|
|
69
|
+
* - transaction.<field> and a bare <field> depend on that field of the transaction itself.
|
|
70
|
+
* A field too many only costs cache hits; a field too few would give wrong results.
|
|
71
|
+
*/
|
|
72
|
+
function getExpressionGranularity(expression) {
|
|
73
|
+
const fields = [];
|
|
74
|
+
const addField = (field) => {
|
|
75
|
+
if (fields.indexOf(field) < 0)
|
|
76
|
+
fields.push(field);
|
|
77
|
+
};
|
|
78
|
+
Object.entries(globalJoinableTables).forEach(([table, joinKey]) => {
|
|
79
|
+
if (expression.includes(table + '.'))
|
|
80
|
+
addField(joinKey);
|
|
81
|
+
});
|
|
82
|
+
const withoutStrings = expression.replace(/"(?:[^"\\]|\\.)*"/g, '""');
|
|
83
|
+
// 'quoted symbol' | symbol, optionally followed by "(" when it is a function call
|
|
84
|
+
const symbolPattern = /'((?:[^'\\]|\\.)*)'|([A-Za-z_$][A-Za-z0-9_$.]*)(\s*\()?/g;
|
|
85
|
+
let match;
|
|
86
|
+
while ((match = symbolPattern.exec(withoutStrings))) {
|
|
87
|
+
let symbol = match[2];
|
|
88
|
+
if (match[1] !== undefined)
|
|
89
|
+
symbol = match[1].replace(/\\(.)/g, '$1');
|
|
90
|
+
else if (match[3] || filtrexKeywords.indexOf(symbol) >= 0)
|
|
91
|
+
continue;
|
|
92
|
+
const positionOfDot = symbol.indexOf('.');
|
|
93
|
+
if (positionOfDot < 0)
|
|
94
|
+
addField(symbol);
|
|
95
|
+
else if (symbol.substring(0, positionOfDot) == 'transaction')
|
|
96
|
+
addField(symbol.substring(positionOfDot + 1).split('.')[0]);
|
|
97
|
+
}
|
|
98
|
+
return fields;
|
|
99
|
+
}
|
|
100
|
+
// Deprecated schema: transactions that still carry a stored vector are added slot by slot.
|
|
54
101
|
function addVectors(targetVector, otherVector, begin, end) {
|
|
102
|
+
if (end > otherVector.length)
|
|
103
|
+
end = otherVector.length;
|
|
55
104
|
for (let i = begin; i < end; i++)
|
|
56
105
|
targetVector[i] += otherVector[i] || 0;
|
|
57
106
|
}
|
|
58
|
-
|
|
59
|
-
function addTransactionToAggregations(beginTimestamp, endTimestamp, aggregateKey, rawAggregations, transaction, maxRows) {
|
|
60
|
-
let aggregateVector = rawAggregations[aggregateKey];
|
|
61
|
-
if (globalNrAggegationExpressions >= maxRows)
|
|
62
|
-
return;
|
|
63
|
-
if (!aggregateVector) {
|
|
64
|
-
globalNrAggegationExpressions++;
|
|
65
|
-
aggregateVector = new Float64Array(VECTOR_LENGTH);
|
|
66
|
-
rawAggregations[aggregateKey] = aggregateVector;
|
|
67
|
-
}
|
|
68
|
-
if (!transaction.vector)
|
|
69
|
-
transaction.vector = transaction_1.default.buildVector(transaction);
|
|
107
|
+
function addStoredVector(beginTimestamp, endTimestamp, aggregateVector, transaction) {
|
|
70
108
|
if (Array.isArray(transaction.vector))
|
|
71
109
|
transaction.vector = Float64Array.from(transaction.vector);
|
|
72
110
|
const v = transaction.vector;
|
|
@@ -118,6 +156,8 @@ function getTrieLeaf(root, transaction, keys) {
|
|
|
118
156
|
}
|
|
119
157
|
return node;
|
|
120
158
|
}
|
|
159
|
+
// The most fields a single transaction adds to an aggregation row (a returned sale: 10)
|
|
160
|
+
const MAX_CONTRIBUTIONS = 12;
|
|
121
161
|
/*
|
|
122
162
|
* A factory function that returns a function that can be used to aggregate transactions
|
|
123
163
|
* The factory function is called once for each aggregationExpression
|
|
@@ -146,19 +186,228 @@ function aggregator({ beginTimestamp, endTimestamp, filterExpression, aggregatio
|
|
|
146
186
|
const filterTrieRoot = {};
|
|
147
187
|
if (filterExpression) {
|
|
148
188
|
filter = (0, filtrex_1.compileExpression)(filterExpression, filtrexOptions);
|
|
149
|
-
filterGranularity =
|
|
150
|
-
.filter((join) => filterExpression.includes(join[0] + '.'))
|
|
151
|
-
.map((join) => join[1]);
|
|
189
|
+
filterGranularity = getExpressionGranularity(filterExpression);
|
|
152
190
|
}
|
|
153
191
|
// Empty aggregation expression = no groupers → only the totals row
|
|
154
192
|
const totalsOnly = !aggregationExpression;
|
|
155
193
|
const aggregationEvaluator = totalsOnly ? undefined : buildAggregationEvaluator(aggregationExpression);
|
|
156
|
-
const aggregationGranularity = totalsOnly
|
|
157
|
-
? []
|
|
158
|
-
: Object.entries(globalJoinableTables)
|
|
159
|
-
.filter((join) => aggregationExpression.includes(join[0] + '.'))
|
|
160
|
-
.map((join) => join[1]);
|
|
194
|
+
const aggregationGranularity = totalsOnly ? [] : getExpressionGranularity(aggregationExpression);
|
|
161
195
|
const aggregationTrieRoot = {};
|
|
196
|
+
let nrNewRows = 0; // tracks the # rows added so we can quit computing if it grows out of bounds
|
|
197
|
+
let totalVector;
|
|
198
|
+
// What the current transaction adds to an aggregation row: computed once, added to its row, subtotal and total
|
|
199
|
+
const slots = new Int32Array(MAX_CONTRIBUTIONS);
|
|
200
|
+
const values = new Float64Array(MAX_CONTRIBUTIONS);
|
|
201
|
+
let nrContributions = 0;
|
|
202
|
+
// NaN (a missing qty or price) counts as 0
|
|
203
|
+
function contribute(slot, value) {
|
|
204
|
+
slots[nrContributions] = slot;
|
|
205
|
+
values[nrContributions++] = value || 0;
|
|
206
|
+
}
|
|
207
|
+
function contributeStockTime(qty, amount, timeLeft) {
|
|
208
|
+
slots[nrContributions] = STOCK_TIME_PRODUCT;
|
|
209
|
+
values[nrContributions++] = qty * timeLeft;
|
|
210
|
+
slots[nrContributions] = STOCKAMOUNT_TIME_PRODUCT;
|
|
211
|
+
values[nrContributions++] = amount * timeLeft;
|
|
212
|
+
}
|
|
213
|
+
// Same numbers as transaction.buildVector, without building a vector per transaction
|
|
214
|
+
function prepareContributions(transaction) {
|
|
215
|
+
nrContributions = 0;
|
|
216
|
+
const time = transaction.time;
|
|
217
|
+
const type = typeof transaction.type == 'string' ? transaction.type : String(transaction.type);
|
|
218
|
+
const qty = +transaction.qty;
|
|
219
|
+
if (time <= beginTimestamp) {
|
|
220
|
+
// Before the period: begin stock, plus outstanding picklists/consignments so endShelf remains stable
|
|
221
|
+
// when only the start date changes for a fixed end date.
|
|
222
|
+
switch (type) {
|
|
223
|
+
case types_1.transactionTypeE.RECEIVING:
|
|
224
|
+
case types_1.transactionTypeE.TRANSIT:
|
|
225
|
+
case types_1.transactionTypeE.CHANGE:
|
|
226
|
+
case types_1.transactionTypeE.START_STOCK:
|
|
227
|
+
case types_1.transactionTypeE.RETURN_CUSTOMER: {
|
|
228
|
+
const cost = (0, index_1.round2)(transaction.buyprice * transaction.qty);
|
|
229
|
+
contribute(QTY_STOCK, qty);
|
|
230
|
+
contribute(AMOUNT_STOCK, cost);
|
|
231
|
+
contribute(QTY_SHELF_STOCK, qty);
|
|
232
|
+
contribute(AMOUNT_SHELF_STOCK, cost);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
case types_1.transactionTypeE.SALE:
|
|
236
|
+
case types_1.transactionTypeE.B2B_SOLD:
|
|
237
|
+
case types_1.transactionTypeE.B2B_RE_SOLD: {
|
|
238
|
+
const cost = (0, index_1.round2)(-transaction.buyprice * transaction.qty);
|
|
239
|
+
contribute(QTY_STOCK, -qty);
|
|
240
|
+
contribute(AMOUNT_STOCK, cost);
|
|
241
|
+
contribute(QTY_SHELF_STOCK, -qty);
|
|
242
|
+
contribute(AMOUNT_SHELF_STOCK, cost);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
case types_1.transactionTypeE.RE_VALUATION: {
|
|
246
|
+
const cost = (0, index_1.round2)(transaction.buyprice * transaction.qty);
|
|
247
|
+
contribute(AMOUNT_STOCK, cost);
|
|
248
|
+
contribute(AMOUNT_SHELF_STOCK, cost);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
case types_1.transactionTypeE.START_SHELF_STOCK:
|
|
252
|
+
// Type 10 subtracts outstanding picktickets of previous shards from the shelf stock
|
|
253
|
+
contribute(QTY_SHELF_STOCK, qty);
|
|
254
|
+
contribute(AMOUNT_SHELF_STOCK, (0, index_1.round2)(transaction.buyprice * transaction.qty));
|
|
255
|
+
return;
|
|
256
|
+
case types_1.transactionTypeE.PICK_LIST:
|
|
257
|
+
contribute(QTY_PICKLIST, qty);
|
|
258
|
+
contribute(AMOUNT_PICKLIST, (0, index_1.round2)(transaction.sellprice * transaction.qty));
|
|
259
|
+
contribute(COSTPRICE_PICKLIST, (0, index_1.round2)(transaction.buyprice * transaction.qty));
|
|
260
|
+
return;
|
|
261
|
+
case types_1.transactionTypeE.CONSIGNMENT:
|
|
262
|
+
contribute(QTY_CONSIGNMENT, qty);
|
|
263
|
+
contribute(AMOUNT_CONSIGNMENT, (0, index_1.round2)(transaction.sellprice * transaction.qty));
|
|
264
|
+
contribute(COSTPRICE_CONSIGNMENT, (0, index_1.round2)(transaction.buyprice * transaction.qty));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (time > endTimestamp)
|
|
270
|
+
return;
|
|
271
|
+
// Period transactions
|
|
272
|
+
const timeLeft = endTimestamp - time;
|
|
273
|
+
switch (type) {
|
|
274
|
+
case types_1.transactionTypeE.SALE: {
|
|
275
|
+
const sku = globalRelations['sku'][transaction.ean];
|
|
276
|
+
const amountSoldExcl = (0, index_1.round2)((transaction.sellprice / ((100 + (transaction.vat || 0)) / 100)) * transaction.qty);
|
|
277
|
+
const costprice = (0, index_1.round2)(transaction.buyprice * transaction.qty);
|
|
278
|
+
const amountSoldMax = (0, index_1.round2)((((sku === null || sku === void 0 ? void 0 : sku.price) || 0) / ((100 + (transaction.vat || (sku === null || sku === void 0 ? void 0 : sku.vat) || 21)) / 100)) * qty);
|
|
279
|
+
contribute(QTY_SOLD, qty);
|
|
280
|
+
contribute(AMOUNT_SOLD, (0, index_1.round2)(transaction.sellprice * transaction.qty));
|
|
281
|
+
contribute(AMOUNT_SOLD_EXCL, amountSoldExcl);
|
|
282
|
+
contribute(COSTPRICE_SOLD, costprice);
|
|
283
|
+
contribute(AMOUNT_SOLD_MAX, amountSoldMax);
|
|
284
|
+
contribute(AMOUNT_SOLD_DISCOUNT, (0, index_1.round2)(amountSoldMax - amountSoldExcl));
|
|
285
|
+
if (qty < 0)
|
|
286
|
+
contribute(QTY_RETURN, qty);
|
|
287
|
+
if (amountSoldExcl < 0)
|
|
288
|
+
contribute(AMOUNT_RETURN, amountSoldExcl);
|
|
289
|
+
contributeStockTime(-(qty || 0), -(costprice || 0), timeLeft);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
case types_1.transactionTypeE.RECEIVING: {
|
|
293
|
+
const cost = (0, index_1.round2)(transaction.buyprice * transaction.qty);
|
|
294
|
+
contribute(QTY_RECIEVED, qty);
|
|
295
|
+
contribute(AMOUNT_RECIEVED, cost);
|
|
296
|
+
contributeStockTime(qty || 0, cost || 0, timeLeft);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
case types_1.transactionTypeE.TRANSIT: {
|
|
300
|
+
const cost = (0, index_1.round2)(transaction.buyprice * transaction.qty);
|
|
301
|
+
contribute(QTY_TRANSIT, qty);
|
|
302
|
+
contribute(AMOUNT_TRANSIT, cost);
|
|
303
|
+
contributeStockTime(qty || 0, cost || 0, timeLeft);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
case types_1.transactionTypeE.CHANGE:
|
|
307
|
+
case types_1.transactionTypeE.START_STOCK: {
|
|
308
|
+
const cost = (0, index_1.round2)(transaction.buyprice * transaction.qty);
|
|
309
|
+
contribute(QTY_CHANGE, qty);
|
|
310
|
+
contribute(AMOUNT_CHANGE, cost);
|
|
311
|
+
contributeStockTime(qty || 0, cost || 0, timeLeft);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
case types_1.transactionTypeE.B2B_SOLD: {
|
|
315
|
+
const cost = (0, index_1.round2)(transaction.buyprice * transaction.qty);
|
|
316
|
+
contribute(QTY_B2B_SOLD, qty);
|
|
317
|
+
contribute(AMOUNT_B2B_SOLD, (0, index_1.round2)(transaction.sellprice * transaction.qty));
|
|
318
|
+
contribute(COSTPRICE_B2B_SOLD, cost);
|
|
319
|
+
contributeStockTime(-(qty || 0), -(cost || 0), timeLeft);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
case types_1.transactionTypeE.B2B_RE_SOLD: {
|
|
323
|
+
const cost = (0, index_1.round2)(transaction.buyprice * transaction.qty);
|
|
324
|
+
contribute(QTY_B2B_RESOLD, qty);
|
|
325
|
+
contribute(AMOUNT_B2B_RESOLD, (0, index_1.round2)(transaction.sellprice * transaction.qty));
|
|
326
|
+
contribute(COSTPRICE_B2B_RESOLD, cost);
|
|
327
|
+
contributeStockTime(-(qty || 0), -(cost || 0), timeLeft);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
case types_1.transactionTypeE.RETURN_CUSTOMER: {
|
|
331
|
+
const cost = (0, index_1.round2)(transaction.buyprice * transaction.qty);
|
|
332
|
+
contribute(QTY_B2B_RETURN, qty);
|
|
333
|
+
contribute(AMOUNT_B2B_RETURN, (0, index_1.round2)(transaction.sellprice * transaction.qty));
|
|
334
|
+
contribute(COSTPRICE_B2B_RETURN, cost);
|
|
335
|
+
contributeStockTime(qty || 0, cost || 0, timeLeft);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
case types_1.transactionTypeE.CREDIT_INVOICE:
|
|
339
|
+
contribute(AMOUNT_B2B_RETURN, (0, index_1.round2)(transaction.sellprice * transaction.qty));
|
|
340
|
+
return;
|
|
341
|
+
case types_1.transactionTypeE.RE_VALUATION:
|
|
342
|
+
contribute(AMOUNT_REVALUATE, (0, index_1.round2)(transaction.buyprice * transaction.qty));
|
|
343
|
+
return;
|
|
344
|
+
case types_1.transactionTypeE.PURCHASE_ORDER:
|
|
345
|
+
contribute(QTY_PO, qty);
|
|
346
|
+
contribute(AMOUNT_PO, (0, index_1.round2)(transaction.buyprice * transaction.qty));
|
|
347
|
+
return;
|
|
348
|
+
case types_1.transactionTypeE.PURCHASE_ORDER_COMPLETE:
|
|
349
|
+
contribute(QTY_PO_COMPLETE, qty);
|
|
350
|
+
contribute(AMOUNT_PO_COMPLETE, (0, index_1.round2)(transaction.buyprice * transaction.qty));
|
|
351
|
+
return;
|
|
352
|
+
case types_1.transactionTypeE.MINIMUM_STOCK:
|
|
353
|
+
contribute(QTY_MINIMUM_STOCK, qty);
|
|
354
|
+
contribute(AMOUNT_MINIMUM_STOCK, (0, index_1.round2)(transaction.buyprice * transaction.qty));
|
|
355
|
+
return;
|
|
356
|
+
case types_1.transactionTypeE.ORDER:
|
|
357
|
+
contribute(QTY_ORDER, qty);
|
|
358
|
+
contribute(AMOUNT_ORDER, (0, index_1.round2)(transaction.sellprice * transaction.qty));
|
|
359
|
+
contribute(COSTPRICE_ORDER, (0, index_1.round2)(transaction.buyprice * transaction.qty));
|
|
360
|
+
return;
|
|
361
|
+
case types_1.transactionTypeE.RE_ORDER:
|
|
362
|
+
contribute(QTY_REORDER, qty);
|
|
363
|
+
contribute(AMOUNT_REORDER, (0, index_1.round2)(transaction.sellprice * transaction.qty));
|
|
364
|
+
contribute(COSTPRICE_REORDER, (0, index_1.round2)(transaction.buyprice * transaction.qty));
|
|
365
|
+
return;
|
|
366
|
+
case types_1.transactionTypeE.PICK_LIST:
|
|
367
|
+
// Picklists reduce shelfStock, which is handled by subtracting QTY_PICKLIST in postAgg
|
|
368
|
+
contribute(QTY_PICKLIST, qty);
|
|
369
|
+
contribute(AMOUNT_PICKLIST, (0, index_1.round2)(transaction.sellprice * transaction.qty));
|
|
370
|
+
contribute(COSTPRICE_PICKLIST, (0, index_1.round2)(transaction.buyprice * transaction.qty));
|
|
371
|
+
return;
|
|
372
|
+
case types_1.transactionTypeE.CONSIGNMENT:
|
|
373
|
+
// Consignments reduce shelfStock, which is handled by subtracting QTY_CONSIGNMENT in postAgg
|
|
374
|
+
contribute(QTY_CONSIGNMENT, qty);
|
|
375
|
+
contribute(AMOUNT_CONSIGNMENT, (0, index_1.round2)(transaction.sellprice * transaction.qty));
|
|
376
|
+
contribute(COSTPRICE_CONSIGNMENT, (0, index_1.round2)(transaction.buyprice * transaction.qty));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Deprecated schema: derived sale fields live in the stored vector
|
|
381
|
+
function prepareStoredVector(transaction) {
|
|
382
|
+
if (transaction.type != types_1.transactionTypeE.SALE)
|
|
383
|
+
return;
|
|
384
|
+
const sku = globalRelations['sku'][transaction.ean];
|
|
385
|
+
if (Array.isArray(transaction.vector)) {
|
|
386
|
+
const tmp = new Float64Array(AMOUNT_SOLD_DISCOUNT + 1);
|
|
387
|
+
tmp.set(transaction.vector);
|
|
388
|
+
transaction.vector = tmp;
|
|
389
|
+
}
|
|
390
|
+
const v = transaction.vector;
|
|
391
|
+
v[AMOUNT_SOLD_MAX] = (0, index_1.round2)((((sku === null || sku === void 0 ? void 0 : sku.price) || 0) / ((100 + (transaction.vat || (sku === null || sku === void 0 ? void 0 : sku.vat) || 21)) / 100)) * v[QTY_SOLD]);
|
|
392
|
+
v[AMOUNT_SOLD_DISCOUNT] = (0, index_1.round2)(v[AMOUNT_SOLD_MAX] - v[AMOUNT_SOLD_EXCL]);
|
|
393
|
+
}
|
|
394
|
+
function getAggregateVector(aggregateKey) {
|
|
395
|
+
let aggregateVector = rawAggregations[aggregateKey];
|
|
396
|
+
if (!aggregateVector) {
|
|
397
|
+
nrNewRows++;
|
|
398
|
+
aggregateVector = new Float64Array(VECTOR_LENGTH);
|
|
399
|
+
rawAggregations[aggregateKey] = aggregateVector;
|
|
400
|
+
}
|
|
401
|
+
return aggregateVector;
|
|
402
|
+
}
|
|
403
|
+
function addTransactionToVector(aggregateVector, transaction) {
|
|
404
|
+
if (transaction.vector)
|
|
405
|
+
return addStoredVector(beginTimestamp, endTimestamp, aggregateVector, transaction);
|
|
406
|
+
if (transaction.type == types_1.transactionTypeE.RECEIVING && transaction.time > aggregateVector[MAX_RECIEVE_TIMESTAMP])
|
|
407
|
+
aggregateVector[MAX_RECIEVE_TIMESTAMP] = transaction.time;
|
|
408
|
+
for (let i = 0; i < nrContributions; i++)
|
|
409
|
+
aggregateVector[slots[i]] += values[i];
|
|
410
|
+
}
|
|
162
411
|
return function (transaction) {
|
|
163
412
|
if (filter) {
|
|
164
413
|
const filterLeaf = getTrieLeaf(filterTrieRoot, transaction, filterGranularity);
|
|
@@ -170,22 +419,15 @@ function aggregator({ beginTimestamp, endTimestamp, filterExpression, aggregatio
|
|
|
170
419
|
return;
|
|
171
420
|
}
|
|
172
421
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
transaction.vector = tmp;
|
|
182
|
-
}
|
|
183
|
-
const v = transaction.vector;
|
|
184
|
-
v[AMOUNT_SOLD_MAX] = (0, index_1.round2)((((sku === null || sku === void 0 ? void 0 : sku.price) || 0) / ((100 + (transaction.vat || (sku === null || sku === void 0 ? void 0 : sku.vat) || 21)) / 100)) * v[QTY_SOLD]);
|
|
185
|
-
v[AMOUNT_SOLD_DISCOUNT] = (0, index_1.round2)(v[AMOUNT_SOLD_MAX] - v[AMOUNT_SOLD_EXCL]);
|
|
422
|
+
if (transaction.vector)
|
|
423
|
+
prepareStoredVector(transaction);
|
|
424
|
+
else
|
|
425
|
+
prepareContributions(transaction);
|
|
426
|
+
if ((totals || totalsOnly) && nrNewRows < maxRows) {
|
|
427
|
+
if (!totalVector)
|
|
428
|
+
totalVector = getAggregateVector(TOTAL_KEY);
|
|
429
|
+
addTransactionToVector(totalVector, transaction);
|
|
186
430
|
}
|
|
187
|
-
if (totals || totalsOnly)
|
|
188
|
-
addTransactionToAggregations(beginTimestamp, endTimestamp, '** TOTAL **', rawAggregations, transaction, maxRows);
|
|
189
431
|
// No detail/subtotal rows when there are no groupers
|
|
190
432
|
if (totalsOnly)
|
|
191
433
|
return;
|
|
@@ -198,20 +440,19 @@ function aggregator({ beginTimestamp, endTimestamp, filterExpression, aggregatio
|
|
|
198
440
|
// Example: for 4 groupers we have rows like
|
|
199
441
|
// g0 g1 g2 g3 <- normal row with no subtotal
|
|
200
442
|
// g0 <- subtotal for g0
|
|
201
|
-
if (subtotalsForOuterGrouper == true) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const KPIKey = prepareSubTotalAggKey(aggregateKey);
|
|
206
|
-
addTransactionToAggregations(beginTimestamp, endTimestamp, KPIKey, rawAggregations, transaction, maxRows);
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
addTransactionToAggregations(beginTimestamp, endTimestamp, aggregateKey, rawAggregations, transaction, maxRows);
|
|
443
|
+
if (subtotalsForOuterGrouper == true && nrNewRows < maxRows) {
|
|
444
|
+
if (!aggregateKeyLeaf.subtotalVector)
|
|
445
|
+
aggregateKeyLeaf.subtotalVector = getAggregateVector(`${aggregateKey.substr(0, aggregateKey.indexOf('\t'))} Σ`);
|
|
446
|
+
addTransactionToVector(aggregateKeyLeaf.subtotalVector, transaction);
|
|
210
447
|
}
|
|
448
|
+
if (nrNewRows >= maxRows)
|
|
449
|
+
return;
|
|
450
|
+
if (!aggregateKeyLeaf.vector)
|
|
451
|
+
aggregateKeyLeaf.vector = getAggregateVector(parentGroupTotals ? prepareSubTotalAggKey(aggregateKey) : aggregateKey);
|
|
452
|
+
addTransactionToVector(aggregateKeyLeaf.vector, transaction);
|
|
211
453
|
};
|
|
212
454
|
}
|
|
213
455
|
function aggregate({ beginTimestamp, endTimestamp, filterExpression, aggregationExpression, transactions, beginStock, relations, rawAggregations, maxRows, totals, subtotalsForOuterGrouper, parentGroupTotals }) {
|
|
214
|
-
globalNrAggegationExpressions = 0;
|
|
215
456
|
globalRelations = relations;
|
|
216
457
|
const compiledAggregator = aggregator({
|
|
217
458
|
beginTimestamp,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface BarcodeCanvasResult {
|
|
2
|
+
dataUrl: string;
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Render a barcode to a PNG data URL.
|
|
8
|
+
* Module width is fixed; the caller must scale the image uniformly into the layout box
|
|
9
|
+
* so bars are not distorted.
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderBarcodeToDataUrl(value: string): BarcodeCanvasResult | null;
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
exports.renderBarcodeToDataUrl = void 0;
|
|
7
|
+
const jsbarcode_1 = __importDefault(require("jsbarcode"));
|
|
8
|
+
/** Vite / CJS interop sometimes wraps the function as `{ default: fn }`. */
|
|
9
|
+
function resolveJsBarcode() {
|
|
10
|
+
const mod = jsbarcode_1.default;
|
|
11
|
+
if (typeof mod === 'function')
|
|
12
|
+
return mod;
|
|
13
|
+
if (mod && typeof mod.default === 'function')
|
|
14
|
+
return mod.default;
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
function createCanvasElement() {
|
|
18
|
+
if (typeof document !== 'undefined' && typeof document.createElement === 'function') {
|
|
19
|
+
return document.createElement('canvas');
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Render a barcode to a PNG data URL.
|
|
25
|
+
* Module width is fixed; the caller must scale the image uniformly into the layout box
|
|
26
|
+
* so bars are not distorted.
|
|
27
|
+
*/
|
|
28
|
+
function renderBarcodeToDataUrl(value) {
|
|
29
|
+
if (!value)
|
|
30
|
+
return null;
|
|
31
|
+
const canvas = createCanvasElement();
|
|
32
|
+
if (!canvas)
|
|
33
|
+
return null;
|
|
34
|
+
const JsBarcode = resolveJsBarcode();
|
|
35
|
+
if (!JsBarcode)
|
|
36
|
+
return null;
|
|
37
|
+
try {
|
|
38
|
+
JsBarcode(canvas, value, {
|
|
39
|
+
// Fixed module geometry — do not stretch independently in X/Y when placing on the page.
|
|
40
|
+
// CODE128 accepts arbitrary values.
|
|
41
|
+
format: 'CODE128',
|
|
42
|
+
width: 2,
|
|
43
|
+
height: 60,
|
|
44
|
+
displayValue: false,
|
|
45
|
+
margin: 0,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
if (typeof console !== 'undefined')
|
|
50
|
+
console.warn('Barcode render failed', value, e);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
if (!canvas.width || !canvas.height)
|
|
54
|
+
return null;
|
|
55
|
+
return {
|
|
56
|
+
dataUrl: canvas.toDataURL('image/png'),
|
|
57
|
+
width: canvas.width,
|
|
58
|
+
height: canvas.height,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
exports.renderBarcodeToDataUrl = renderBarcodeToDataUrl;
|
package/dist/pdf.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface PaperSize {
|
|
|
5
5
|
footerHeight: number;
|
|
6
6
|
}
|
|
7
7
|
export type TextAlign = 1 | 2 | 3;
|
|
8
|
-
export type ObjectType = 'text' | 'field' | 'rectangle' | 'image' | 'container';
|
|
8
|
+
export type ObjectType = 'text' | 'field' | 'rectangle' | 'image' | 'container' | 'barcode';
|
|
9
9
|
export type RepeatContainer = 'horizontal' | 'vertical' | undefined;
|
|
10
10
|
export interface BaseLayoutObject {
|
|
11
11
|
type: ObjectType;
|
|
@@ -44,6 +44,14 @@ export interface ImageLayoutObject extends BaseLayoutObject {
|
|
|
44
44
|
url?: string;
|
|
45
45
|
image?: HTMLImageElement;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Renders a 1D barcode from printBuffer via `source` (default `barcode`).
|
|
49
|
+
* Drawn with uniform scale to fit width×height (never independently stretched).
|
|
50
|
+
*/
|
|
51
|
+
export interface BarcodeLayoutObject extends BaseLayoutObject {
|
|
52
|
+
type: 'barcode';
|
|
53
|
+
source?: string;
|
|
54
|
+
}
|
|
47
55
|
export interface ContainerLayoutObject extends BaseLayoutObject {
|
|
48
56
|
type: 'container';
|
|
49
57
|
source: string;
|
|
@@ -61,7 +69,7 @@ export interface ContainerLayoutObject extends BaseLayoutObject {
|
|
|
61
69
|
*/
|
|
62
70
|
printOnlyAtStart?: boolean;
|
|
63
71
|
}
|
|
64
|
-
export type LayoutObject = TextLayoutObject | FieldLayoutObject | RectangleLayoutObject | ImageLayoutObject | ContainerLayoutObject;
|
|
72
|
+
export type LayoutObject = TextLayoutObject | FieldLayoutObject | RectangleLayoutObject | ImageLayoutObject | BarcodeLayoutObject | ContainerLayoutObject;
|
|
65
73
|
export interface Layout {
|
|
66
74
|
name?: string;
|
|
67
75
|
group?: string;
|
package/dist/reports.js
CHANGED
|
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.genPDF = exports.computeImageDrawRect = void 0;
|
|
16
16
|
const jspdf_1 = require("jspdf");
|
|
17
17
|
const date_fns_1 = require("date-fns");
|
|
18
|
+
const barcodeCanvas_1 = require("./barcodeCanvas");
|
|
18
19
|
const deepCopy_1 = __importDefault(require("./deepCopy"));
|
|
19
20
|
const layoutFieldFormat_1 = require("./layoutFieldFormat");
|
|
20
21
|
/** Measurement uses context `currentPageNumber` (1-based). Rendering uses jsPDF’s `getCurrentPageInfo().pageNumber`, which is already 1-based (first page is 1). */
|
|
@@ -287,7 +288,7 @@ function truncateTextToFitWidth(doc, text, maxWidth) {
|
|
|
287
288
|
return truncatedText + ELLIPSIS;
|
|
288
289
|
}
|
|
289
290
|
function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options, context) {
|
|
290
|
-
var _a, _b, _c, _d;
|
|
291
|
+
var _a, _b, _c, _d, _f;
|
|
291
292
|
let text = ''; // Will hold text to display for either 'text' or 'field'
|
|
292
293
|
// Get text from object for text objects
|
|
293
294
|
if (object.type == 'text') {
|
|
@@ -377,6 +378,28 @@ function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options
|
|
|
377
378
|
}
|
|
378
379
|
}
|
|
379
380
|
}
|
|
381
|
+
// Draw barcode for barcode objects (uniform scale into the layout box — never stretch bars)
|
|
382
|
+
if (object.type == 'barcode') {
|
|
383
|
+
const barcodeObj = object;
|
|
384
|
+
const source = barcodeObj.source || 'barcode';
|
|
385
|
+
const raw = getProperty(source, printBuffer);
|
|
386
|
+
const value = raw == null ? '' : String(raw);
|
|
387
|
+
if (!value) {
|
|
388
|
+
(_d = options.onAlert) === null || _d === void 0 ? void 0 : _d.call(options, { header: 'fieldnotfound', body: source, type: 'warning', timeout: 10000 });
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
const rendered = (0, barcodeCanvas_1.renderBarcodeToDataUrl)(value);
|
|
392
|
+
if (rendered) {
|
|
393
|
+
const { x: drawX, y: drawY, width: drawWidth, height: drawHeight } = computeImageDrawRect(x, y, width, height, rendered.width, rendered.height);
|
|
394
|
+
try {
|
|
395
|
+
doc.addImage(rendered.dataUrl, 'PNG', drawX, drawY, drawWidth, drawHeight);
|
|
396
|
+
}
|
|
397
|
+
catch (_e) {
|
|
398
|
+
// Skip if jsPDF cannot embed the canvas data URL
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
380
403
|
// Draw text for text and field objets
|
|
381
404
|
if ((object.type == 'text' || object.type == 'field') && text) {
|
|
382
405
|
let textAlign;
|
|
@@ -394,7 +417,7 @@ function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options
|
|
|
394
417
|
return;
|
|
395
418
|
const fontStyleStr = FONT_STYLES[object.fontStyle & 3] || 'normal';
|
|
396
419
|
// Use object font if it's a custom font; otherwise layout default overrides standard fonts (Helvetica etc.)
|
|
397
|
-
const fontFamily = object.fontFamily && ((
|
|
420
|
+
const fontFamily = object.fontFamily && ((_f = options.isCustomPdfFont) === null || _f === void 0 ? void 0 : _f.call(options, object.fontFamily)) ? object.fontFamily : context.defaultFontFamily || object.fontFamily || 'Helvetica';
|
|
398
421
|
doc.setFont(fontFamily, fontStyleStr);
|
|
399
422
|
doc.setFontSize(object.fontSize / 4);
|
|
400
423
|
doc.text(truncateTextToFitWidth(doc, text, width), x, y, { align: textAlign, baseline: 'hanging' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softwear/latestcollectioncore",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.207",
|
|
4
4
|
"description": "Core functions for LatestCollections applications",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"date-fns": "^2.29.3",
|
|
40
40
|
"filtrex": "^2.2.3",
|
|
41
|
+
"jsbarcode": "^3.12.3",
|
|
41
42
|
"jspdf": "^4.2.1"
|
|
42
43
|
}
|
|
43
44
|
}
|