@softwear/latestcollectioncore 1.0.138 → 1.0.139

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.
@@ -75,7 +75,16 @@ function addTransactionToAggregations(beginTimestamp, endTimestamp, aggregateKey
75
75
  }
76
76
  if (transaction.time > endTimestamp)
77
77
  return;
78
+ // Period transactions: add everything from BEGIN_STOCK_VECTOR_END onwards
78
79
  addVectors(aggregateVector, v, BEGIN_STOCK_VECTOR_END, v.length);
80
+ // Also update QTY_SHELF_STOCK and AMOUNT_SHELF_STOCK from period transactions
81
+ // (they're in the begin stock range but need to be updated by period transactions too)
82
+ if (v[QTY_SHELF_STOCK] !== undefined) {
83
+ aggregateVector[QTY_SHELF_STOCK] += v[QTY_SHELF_STOCK] || 0;
84
+ }
85
+ if (v[AMOUNT_SHELF_STOCK] !== undefined) {
86
+ aggregateVector[AMOUNT_SHELF_STOCK] += v[AMOUNT_SHELF_STOCK] || 0;
87
+ }
79
88
  // Calculate derived fields
80
89
  aggregateVector[QTY_RETURN] += v[QTY_SOLD] < 0 ? v[QTY_SOLD] : 0;
81
90
  aggregateVector[AMOUNT_RETURN] += v[AMOUNT_SOLD_EXCL] < 0 ? v[AMOUNT_SOLD_EXCL] : 0;
@@ -254,8 +263,14 @@ function postAgg(v, timeFrame) {
254
263
  const totalCostSold = v[COSTPRICE_SOLD] + v[COSTPRICE_B2B_SOLD] + v[COSTPRICE_B2B_RESOLD] - v[COSTPRICE_B2B_RETURN];
255
264
  v[QTY_END_STOCK] = v[QTY_STOCK] + v[QTY_RECIEVED] + v[QTY_CHANGE] + v[QTY_TRANSIT] - totalQtySold;
256
265
  v[AMOUNT_END_STOCK] = v[AMOUNT_STOCK] + v[AMOUNT_RECIEVED] + v[AMOUNT_CHANGE] + v[AMOUNT_TRANSIT] + v[AMOUNT_REVALUATE] - totalCostSold;
257
- v[QTY_END_SHELF_STOCK] = v[QTY_END_STOCK] - v[QTY_CONSIGNMENT] - v[QTY_PICKLIST];
258
- v[AMOUNT_END_SHELF_STOCK] = v[AMOUNT_END_STOCK] - v[COSTPRICE_CONSIGNMENT] - v[AMOUNT_PICKLIST];
266
+ // QTY_SHELF_STOCK includes:
267
+ // - beginStock from type 0 (same as QTY_STOCK)
268
+ // - beginShelfStock adjustment from type 10 (negative picktickets from previous shards)
269
+ // - shelfStock-affecting transactions during the period (type 1, type 2, etc.)
270
+ // So QTY_SHELF_STOCK already represents the shelfStock value including all transactions
271
+ // endShelfStock = QTY_SHELF_STOCK - consignment - picklist
272
+ v[QTY_END_SHELF_STOCK] = v[QTY_SHELF_STOCK] - v[QTY_CONSIGNMENT] - v[QTY_PICKLIST];
273
+ v[AMOUNT_END_SHELF_STOCK] = v[AMOUNT_SHELF_STOCK] - v[COSTPRICE_CONSIGNMENT] - v[AMOUNT_PICKLIST];
259
274
  v[SELLOUT_PERCENTAGE] = (totalQtySold / (totalQtySold + v[QTY_END_STOCK])) * 100;
260
275
  v[ROI] = totalAmountSold - v[AMOUNT_RECIEVED] + v[AMOUNT_TRANSIT];
261
276
  v[QTY_AVG_STOCK] = (v[QTY_STOCK] * timeFrame + v[STOCK_TIME_PRODUCT]) / timeFrame;
@@ -138,6 +138,8 @@ const buildVector = function (transaction) {
138
138
  vector[tv.QTY_TRANSACTION] = transaction.qty;
139
139
  vector[tv.QTY_STOCK] = transaction.qty;
140
140
  vector[tv.AMOUNT_STOCK] = (0, index_1.round2)(transaction.buyprice * transaction.qty);
141
+ // Type 0 (START_STOCK) sets QTY_SHELF_STOCK to same value as QTY_STOCK
142
+ // Type 10 (START_SHELF_STOCK) will subtract outstanding picktickets from this
141
143
  vector[tv.QTY_SHELF_STOCK] = transaction.qty;
142
144
  vector[tv.AMOUNT_SHELF_STOCK] = (0, index_1.round2)(transaction.buyprice * transaction.qty);
143
145
  vector[tv.QTY_CHANGE] = transaction.qty;
@@ -197,10 +199,11 @@ const buildVector = function (transaction) {
197
199
  return vector;
198
200
  }
199
201
  if (transaction.type == types_1.transactionTypeE.PICK_LIST) {
202
+ // Type 94 (PICK_LIST) should NOT set QTY_SHELF_STOCK
203
+ // Picklists reduce shelfStock, which is handled by subtracting QTY_PICKLIST in postAgg
200
204
  const vector = new Float64Array(tv.COSTPRICE_PICKLIST + 1);
201
205
  vector[tv.QTY_TRANSACTION] = transaction.qty;
202
- vector[tv.QTY_SHELF_STOCK] = transaction.qty;
203
- vector[tv.AMOUNT_SHELF_STOCK] = (0, index_1.round2)(transaction.buyprice * transaction.qty);
206
+ // QTY_SHELF_STOCK is NOT set - picklists are subtracted separately via QTY_PICKLIST
204
207
  vector[tv.QTY_PICKLIST] = transaction.qty;
205
208
  vector[tv.AMOUNT_PICKLIST] = (0, index_1.round2)(transaction.sellprice * transaction.qty);
206
209
  vector[tv.COSTPRICE_PICKLIST] = (0, index_1.round2)(transaction.buyprice * transaction.qty);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwear/latestcollectioncore",
3
- "version": "1.0.138",
3
+ "version": "1.0.139",
4
4
  "description": "Core functions for LatestCollections applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -137,7 +137,16 @@ function addTransactionToAggregations(beginTimestamp: number, endTimestamp: numb
137
137
  return
138
138
  }
139
139
  if (transaction.time > endTimestamp) return
140
+ // Period transactions: add everything from BEGIN_STOCK_VECTOR_END onwards
140
141
  addVectors(aggregateVector, v, BEGIN_STOCK_VECTOR_END, v.length)
142
+ // Also update QTY_SHELF_STOCK and AMOUNT_SHELF_STOCK from period transactions
143
+ // (they're in the begin stock range but need to be updated by period transactions too)
144
+ if (v[QTY_SHELF_STOCK] !== undefined) {
145
+ aggregateVector[QTY_SHELF_STOCK] += v[QTY_SHELF_STOCK] || 0
146
+ }
147
+ if (v[AMOUNT_SHELF_STOCK] !== undefined) {
148
+ aggregateVector[AMOUNT_SHELF_STOCK] += v[AMOUNT_SHELF_STOCK] || 0
149
+ }
141
150
 
142
151
  // Calculate derived fields
143
152
 
@@ -336,8 +345,14 @@ function postAgg(v: any, timeFrame: number): void {
336
345
 
337
346
  v[QTY_END_STOCK] = v[QTY_STOCK] + v[QTY_RECIEVED] + v[QTY_CHANGE] + v[QTY_TRANSIT] - totalQtySold
338
347
  v[AMOUNT_END_STOCK] = v[AMOUNT_STOCK] + v[AMOUNT_RECIEVED] + v[AMOUNT_CHANGE] + v[AMOUNT_TRANSIT] + v[AMOUNT_REVALUATE] - totalCostSold
339
- v[QTY_END_SHELF_STOCK] = v[QTY_END_STOCK] - v[QTY_CONSIGNMENT] - v[QTY_PICKLIST]
340
- v[AMOUNT_END_SHELF_STOCK] = v[AMOUNT_END_STOCK] - v[COSTPRICE_CONSIGNMENT] - v[AMOUNT_PICKLIST]
348
+ // QTY_SHELF_STOCK includes:
349
+ // - beginStock from type 0 (same as QTY_STOCK)
350
+ // - beginShelfStock adjustment from type 10 (negative picktickets from previous shards)
351
+ // - shelfStock-affecting transactions during the period (type 1, type 2, etc.)
352
+ // So QTY_SHELF_STOCK already represents the shelfStock value including all transactions
353
+ // endShelfStock = QTY_SHELF_STOCK - consignment - picklist
354
+ v[QTY_END_SHELF_STOCK] = v[QTY_SHELF_STOCK] - v[QTY_CONSIGNMENT] - v[QTY_PICKLIST]
355
+ v[AMOUNT_END_SHELF_STOCK] = v[AMOUNT_SHELF_STOCK] - v[COSTPRICE_CONSIGNMENT] - v[AMOUNT_PICKLIST]
341
356
  v[SELLOUT_PERCENTAGE] = (totalQtySold / (totalQtySold + v[QTY_END_STOCK])) * 100
342
357
  v[ROI] = totalAmountSold - v[AMOUNT_RECIEVED] + v[AMOUNT_TRANSIT]
343
358
  v[QTY_AVG_STOCK] = (v[QTY_STOCK] * timeFrame + v[STOCK_TIME_PRODUCT]) / timeFrame
@@ -146,6 +146,8 @@ const buildVector = function (transaction: TransactionI): Float64Array {
146
146
  vector[tv.QTY_TRANSACTION] = transaction.qty
147
147
  vector[tv.QTY_STOCK] = transaction.qty
148
148
  vector[tv.AMOUNT_STOCK] = round2(transaction.buyprice * transaction.qty)
149
+ // Type 0 (START_STOCK) sets QTY_SHELF_STOCK to same value as QTY_STOCK
150
+ // Type 10 (START_SHELF_STOCK) will subtract outstanding picktickets from this
149
151
  vector[tv.QTY_SHELF_STOCK] = transaction.qty
150
152
  vector[tv.AMOUNT_SHELF_STOCK] = round2(transaction.buyprice * transaction.qty)
151
153
  vector[tv.QTY_CHANGE] = transaction.qty
@@ -213,10 +215,11 @@ const buildVector = function (transaction: TransactionI): Float64Array {
213
215
  }
214
216
 
215
217
  if (transaction.type == transactionTypeE.PICK_LIST) {
218
+ // Type 94 (PICK_LIST) should NOT set QTY_SHELF_STOCK
219
+ // Picklists reduce shelfStock, which is handled by subtracting QTY_PICKLIST in postAgg
216
220
  const vector = new Float64Array(tv.COSTPRICE_PICKLIST + 1)
217
221
  vector[tv.QTY_TRANSACTION] = transaction.qty
218
- vector[tv.QTY_SHELF_STOCK] = transaction.qty
219
- vector[tv.AMOUNT_SHELF_STOCK] = round2(transaction.buyprice * transaction.qty)
222
+ // QTY_SHELF_STOCK is NOT set - picklists are subtracted separately via QTY_PICKLIST
220
223
  vector[tv.QTY_PICKLIST] = transaction.qty
221
224
  vector[tv.AMOUNT_PICKLIST] = round2(transaction.sellprice * transaction.qty)
222
225
  vector[tv.COSTPRICE_PICKLIST] = round2(transaction.buyprice * transaction.qty)
@@ -267,6 +267,125 @@ describe('articleStatus', () => {
267
267
  expect(result[2].vector[articleStatus.VALUE_AVG_STOCK]).toBe(90.00000031709791)
268
268
  expect(result[2].vector[articleStatus.MARGIN]).toBe(50)
269
269
  })
270
+
271
+ it('should calculate shelfStock correctly with type 10 (beginShelfStock) and type 94 (picklist)', () => {
272
+ // Test scenario:
273
+ // - beginStock (type 0): 100 units
274
+ // - beginShelfStock (type 10): -5 units (outstanding picktickets from previous shard)
275
+ // - receiving (type 1): +20 units
276
+ // - picklist (type 94): +3 units (new pickticket created)
277
+ // - sale (type 2): -2 units
278
+ // Expected endStock: 100 + 20 - 2 = 118
279
+ // Expected endShelfStock: 118 + (-5) - 0 - 3 = 110
280
+ // (endStock + beginShelfStock - consignment - picklist)
281
+
282
+ const shelfStockTransactions = [
283
+ {
284
+ time: 1546297200000, // Before beginTimestamp - beginStock
285
+ type: '0',
286
+ ean: 'TEST001',
287
+ wh: '50',
288
+ qty: 100,
289
+ buyprice: 10.0,
290
+ docnr: 'BEGINSTOCK',
291
+ },
292
+ {
293
+ time: 1546297200000, // Before beginTimestamp - beginShelfStock (negative picktickets)
294
+ type: '10',
295
+ ean: 'TEST001',
296
+ wh: '50',
297
+ qty: -5, // Negative: outstanding picktickets from previous shard
298
+ buyprice: 10.0,
299
+ docnr: 'PT-001',
300
+ },
301
+ {
302
+ time: 1569316408000, // Within timeframe - receiving
303
+ type: '1',
304
+ ean: 'TEST001',
305
+ wh: '50',
306
+ qty: 20,
307
+ buyprice: 10.0,
308
+ docnr: 'REC-001',
309
+ },
310
+ {
311
+ time: 1569316408500, // Within timeframe - picklist
312
+ type: '94',
313
+ ean: 'TEST001',
314
+ wh: '50',
315
+ qty: 3,
316
+ buyprice: 10.0,
317
+ sellprice: 15.0,
318
+ docnr: 'PT-002',
319
+ },
320
+ {
321
+ time: 1569316409000, // Within timeframe - sale
322
+ type: '2',
323
+ ean: 'TEST001',
324
+ wh: '50',
325
+ qty: 2,
326
+ buyprice: 10.0,
327
+ sellprice: 20.0,
328
+ vat: 21,
329
+ docnr: 'SALE-001',
330
+ },
331
+ ]
332
+
333
+ const rawAggregations = {}
334
+ articleStatus.runQuery(
335
+ { beginTimeStamp: 1569316407000, endTimeStamp: 1569316409000 + msYear },
336
+ rawAggregations,
337
+ true, // beginStock = true, so type 0 and type 10 are included
338
+ shelfStockTransactions,
339
+ ['transaction.ean'],
340
+ {},
341
+ {},
342
+ {},
343
+ false,
344
+ '',
345
+ false
346
+ )
347
+
348
+ const result = []
349
+ Object.entries(rawAggregations).map((oneAggregatedRow) => {
350
+ const vector = [].slice.call(oneAggregatedRow[1])
351
+ articleStatus.postAgg(vector, msYear)
352
+ result.push({ key: oneAggregatedRow[0], vector })
353
+ })
354
+
355
+ // Find the TEST001 result
356
+ const testResult = result.find((r) => r.key === 'TEST001')
357
+ expect(testResult).toBeDefined()
358
+
359
+ // Verify calculations
360
+ // beginStock: 100
361
+ expect(testResult.vector[articleStatus.QTY_STOCK]).toBe(100)
362
+ expect(testResult.vector[articleStatus.AMOUNT_STOCK]).toBe(1000)
363
+
364
+ // QTY_SHELF_STOCK includes beginShelfStock (100 - 5 = 95) + period transactions (20 - 2 = 18) = 113
365
+ // beginShelfStock from beginTimestamp: 100 (type 0) - 5 (type 10) = 95
366
+ // Period transactions: +20 (receiving) - 2 (sale) = +18
367
+ // Total: 95 + 18 = 113
368
+ expect(testResult.vector[articleStatus.QTY_SHELF_STOCK]).toBe(113)
369
+ expect(testResult.vector[articleStatus.AMOUNT_SHELF_STOCK]).toBe(1130)
370
+
371
+ // Transactions during period: +20 (receiving) - 2 (sale) = +18
372
+ // endStock: 100 + 20 - 2 = 118
373
+ expect(testResult.vector[articleStatus.QTY_END_STOCK]).toBe(118)
374
+ expect(testResult.vector[articleStatus.AMOUNT_END_STOCK]).toBe(1180)
375
+
376
+ // picklist: 3
377
+ expect(testResult.vector[articleStatus.QTY_PICKLIST]).toBe(3)
378
+ expect(testResult.vector[articleStatus.AMOUNT_PICKLIST]).toBe(45)
379
+
380
+ // consignment: 0 (not in test data)
381
+ expect(testResult.vector[articleStatus.QTY_CONSIGNMENT]).toBe(0)
382
+
383
+ // endShelfStock = QTY_SHELF_STOCK - consignment - picklist
384
+ // = 113 - 0 - 3 = 110
385
+ expect(testResult.vector[articleStatus.QTY_END_SHELF_STOCK]).toBe(110)
386
+ expect(testResult.vector[articleStatus.AMOUNT_END_SHELF_STOCK]).toBe(1085)
387
+ })
388
+
270
389
  it('#getDateRangeFromPicker', () => {
271
390
  const dateRange = articleStatus.getDateRangeFromPicker(['2024-01-01', '2024-12-31'])
272
391
  expect(dateRange.beginTimeStamp).toBe(1704063600000)
@@ -356,7 +356,9 @@ describe('#Transactions', () => {
356
356
  wh: '51',
357
357
  time: 314159265,
358
358
  docnr: '51-123',
359
- vector: [123, 0, 0, 123, 1223.85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 123 * 19.95, 123 * 9.95],
359
+ // Type 94 (PICK_LIST) does NOT set QTY_SHELF_STOCK or AMOUNT_SHELF_STOCK
360
+ // Picklists reduce shelfStock, which is handled by subtracting QTY_PICKLIST in postAgg
361
+ vector: [123, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 123 * 19.95, 123 * 9.95],
360
362
  })
361
363
  })
362
364