@dhyasama/totem-models 11.135.0 → 11.137.0

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.
Files changed (2) hide show
  1. package/lib/Financials.js +161 -11
  2. package/package.json +1 -1
package/lib/Financials.js CHANGED
@@ -85,9 +85,7 @@ module.exports = function(mongoose, config) {
85
85
 
86
86
  year: { type: Number, default: 0 },
87
87
  period: { type: String, enum: ['FY', 'H1', 'H2', 'Q1', 'Q2', 'Q3', 'Q4', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] },
88
-
89
- beginDate: { type: Date },
90
- endDate: { type: Date },
88
+ cuttoff: { type: Date },
91
89
 
92
90
  form: [{
93
91
  name: { type: String, trim: true },
@@ -145,7 +143,32 @@ module.exports = function(mongoose, config) {
145
143
  timestamp: { type: Date, required: true, default: Date.now },
146
144
  user: { type: String, trim: true, required: true },
147
145
  action: { type: String, enum: ['updated', 'deleted', 'verified'], required: true },
148
- }]
146
+ }],
147
+ previous: {
148
+ _id: false,
149
+ value: { type: Number },
150
+ breakdown: { type: Schema.Types.Mixed },
151
+ source: {
152
+ _id: false,
153
+ model: { type: String, enum: ['Document', 'Meeting', 'Message', 'Note'] },
154
+ ref: { type: Schema.Types.ObjectId },
155
+ position: {
156
+ _id: false,
157
+ page: { type: Number },
158
+ bbox: { _id: false, top: { type: Number }, left: { type: Number }, width: { type: Number }, height: { type: Number } },
159
+ row: { type: Number },
160
+ col: { type: Number },
161
+ sheet: { type: String }
162
+ }
163
+ },
164
+ verified: { type: Boolean },
165
+ history: [{
166
+ _id: false,
167
+ timestamp: { type: Date },
168
+ user: { type: String, trim: true },
169
+ action: { type: String, enum: ['updated', 'deleted', 'verified'] },
170
+ }]
171
+ }
149
172
  }],
150
173
 
151
174
  questions: [{
@@ -369,12 +392,21 @@ module.exports = function(mongoose, config) {
369
392
  console.log('Upserting metrics for snapshot', snapshotUUID)
370
393
  console.log('Metrics to upsert:', metricsToUpdate)
371
394
 
372
- session.then(_session => {
395
+ session.then(async _session => {
373
396
 
374
397
  _session.startTransaction();
375
398
 
376
399
  console.log('started session');
377
400
 
401
+ // Fetch the current document to read existing metric values before overwriting
402
+ let currentDoc = null;
403
+ try {
404
+ currentDoc = await self.findOne({ _id: financialsId, 'snapshots.uuid': snapshotUUID }, null, { session: _session });
405
+ } catch (e) {
406
+ console.log('Could not fetch current metrics for previous state:', e);
407
+ }
408
+ const currentSnapshot = currentDoc ? currentDoc.snapshots.find(s => s.uuid === snapshotUUID) : null;
409
+
378
410
  const promises = metricsToUpdate.map(metric => {
379
411
 
380
412
  console.log('Upserting metric:', metric);
@@ -383,9 +415,32 @@ module.exports = function(mongoose, config) {
383
415
  console.log('Upserting metric value:', metric.value);
384
416
  console.log('Upserting metric breakdown:', metric.breakdown);
385
417
 
418
+ // Capture the current metric state to save as `previous`
419
+ // Use null (not undefined) for missing fields so MongoDB persists them
420
+ const currentMetric = currentSnapshot ? currentSnapshot.metrics.find(m => m.name === metric.name && m.scenario === metric.scenario) : null;
421
+ const previousState = currentMetric ? {
422
+ value: currentMetric.value != null ? currentMetric.value : null,
423
+ breakdown: currentMetric.breakdown != null ? currentMetric.breakdown : null,
424
+ source: currentMetric.source || null,
425
+ verified: currentMetric.verified != null ? currentMetric.verified : null,
426
+ history: currentMetric.history || []
427
+ } : undefined;
428
+
386
429
  // Check if the metric exists and update it
387
430
  // Use $elemMatch to ensure we find a metric with BOTH name AND scenario matching
388
431
  // (without $elemMatch, MongoDB could match name on one metric and scenario on another)
432
+ const $set = {
433
+ 'snapshots.$.metrics.$[metric].value': metric.value,
434
+ 'snapshots.$.metrics.$[metric].breakdown': metric.breakdown,
435
+ 'snapshots.$.metrics.$[metric].source': metric.source,
436
+ 'snapshots.$.metrics.$[metric].verified': metric.verified
437
+ };
438
+
439
+ // Save previous state if the metric already exists
440
+ if (previousState) {
441
+ $set['snapshots.$.metrics.$[metric].previous'] = previousState;
442
+ }
443
+
389
444
  return self.findOneAndUpdate(
390
445
  {
391
446
  _id: financialsId,
@@ -402,12 +457,7 @@ module.exports = function(mongoose, config) {
402
457
  }
403
458
  },
404
459
  {
405
- $set: {
406
- 'snapshots.$.metrics.$[metric].value': metric.value,
407
- 'snapshots.$.metrics.$[metric].breakdown': metric.breakdown,
408
- 'snapshots.$.metrics.$[metric].source': metric.source,
409
- 'snapshots.$.metrics.$[metric].verified': metric.verified
410
- },
460
+ $set: $set,
411
461
  $push: {
412
462
  'snapshots.$.metrics.$[metric].history': {
413
463
  $each: metric.history || [],
@@ -462,6 +512,95 @@ module.exports = function(mongoose, config) {
462
512
 
463
513
  }
464
514
 
515
+ Financials.statics.undoMetrics = function undoMetrics(financialsId, snapshotUUID, metricsToUndo, cb) {
516
+
517
+ const self = this;
518
+ const session = mongoose.startSession();
519
+
520
+ session.then(async _session => {
521
+
522
+ _session.startTransaction();
523
+
524
+ try {
525
+
526
+ const currentDoc = await self.findOne({ _id: financialsId, 'snapshots.uuid': snapshotUUID }, null, { session: _session });
527
+ if (!currentDoc) { throw new Error('Financials not found'); }
528
+
529
+ const snapshot = currentDoc.snapshots.find(s => s.uuid === snapshotUUID);
530
+ if (!snapshot) { throw new Error('Snapshot not found'); }
531
+
532
+ const promises = metricsToUndo.map(metricId => {
533
+
534
+ const currentMetric = snapshot.metrics.find(m => m.name === metricId.name && m.scenario === metricId.scenario);
535
+ if (!currentMetric || !currentMetric.previous) {
536
+ console.log('No previous state for metric:', metricId.name, metricId.scenario);
537
+ return Promise.resolve();
538
+ }
539
+
540
+ const prev = currentMetric.previous;
541
+ const prefix = 'snapshots.$.metrics.$[metric]';
542
+
543
+ // Restore fields that had values; unset fields that were null (didn't exist before)
544
+ const $set = {};
545
+ const $unset = { [prefix + '.previous']: 1 };
546
+
547
+ const fields = ['value', 'breakdown', 'source', 'verified'];
548
+ fields.forEach(field => {
549
+ if (prev[field] != null) {
550
+ $set[prefix + '.' + field] = prev[field];
551
+ } else {
552
+ $unset[prefix + '.' + field] = 1;
553
+ }
554
+ });
555
+
556
+ // History is always restored (empty array if it was empty)
557
+ $set[prefix + '.history'] = prev.history || [];
558
+
559
+ return self.findOneAndUpdate(
560
+ {
561
+ _id: financialsId,
562
+ snapshots: {
563
+ $elemMatch: {
564
+ uuid: snapshotUUID,
565
+ metrics: {
566
+ $elemMatch: {
567
+ name: metricId.name,
568
+ scenario: metricId.scenario
569
+ }
570
+ }
571
+ }
572
+ }
573
+ },
574
+ {
575
+ $set: $set,
576
+ $unset: $unset
577
+ },
578
+ {
579
+ arrayFilters: [{ 'metric.name': metricId.name, 'metric.scenario': metricId.scenario }],
580
+ new: true,
581
+ session: _session
582
+ }
583
+ );
584
+
585
+ });
586
+
587
+ await Promise.all(promises);
588
+ await _session.commitTransaction();
589
+ _session.endSession();
590
+ console.log('Undo complete.');
591
+ return cb();
592
+
593
+ } catch (error) {
594
+ await _session.abortTransaction();
595
+ _session.endSession();
596
+ console.error(error);
597
+ return cb(error);
598
+ }
599
+
600
+ });
601
+
602
+ }
603
+
465
604
  Financials.statics.getAll = function getAll(options, cb) {
466
605
 
467
606
  const self = this;
@@ -1102,6 +1241,17 @@ module.exports = function(mongoose, config) {
1102
1241
 
1103
1242
  updateStatus(self);
1104
1243
 
1244
+ // Wipe out previous values on metrics when a metric is verified
1245
+ _.each(self.snapshots, function(snapshot) {
1246
+ if (snapshot) {
1247
+ _.each(snapshot.metrics, function(metric) {
1248
+ if (metric && metric.verified && metric.previous) {
1249
+ metric.previous = undefined;
1250
+ }
1251
+ });
1252
+ }
1253
+ });
1254
+
1105
1255
  return next();
1106
1256
 
1107
1257
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dhyasama/totem-models",
3
- "version": "11.135.0",
3
+ "version": "11.137.0",
4
4
  "author": "Jason Reynolds",
5
5
  "license": "UNLICENSED",
6
6
  "description": "Models for Totem platform",