@dhyasama/totem-models 12.23.0 → 12.25.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.
package/lib/Financials.js
CHANGED
|
@@ -76,6 +76,9 @@ module.exports = function(mongoose, config) {
|
|
|
76
76
|
|
|
77
77
|
uuid: { type: String },
|
|
78
78
|
|
|
79
|
+
// DEPRECATED: currency/scale now live per-metric (see metrics[].currency / metrics[].scale).
|
|
80
|
+
// Retained during the dual-write transition as the fallback default for un-migrated metrics;
|
|
81
|
+
// remove once all read paths resolve currency/scale per metric.
|
|
79
82
|
currency: { type: String, default: 'USD' },
|
|
80
83
|
scale: { type: Number, enum: [1, 1000, 1000000, 1000000000, 1000000000000], default: 1 },
|
|
81
84
|
|
|
@@ -145,6 +148,11 @@ module.exports = function(mongoose, config) {
|
|
|
145
148
|
}
|
|
146
149
|
}],
|
|
147
150
|
verified: { type: Boolean, default: false },
|
|
151
|
+
// Per-metric currency/scale. Intentionally NO defaults: an undefined value means
|
|
152
|
+
// "not yet migrated" and read paths fall back to the (deprecated) snapshot-level
|
|
153
|
+
// currency/scale. See scripts/migrate-financials-schema.js for the backfill.
|
|
154
|
+
currency: { type: String, trim: true },
|
|
155
|
+
scale: { type: Number, enum: [1, 1000, 1000000, 1000000000, 1000000000000] },
|
|
148
156
|
history: [{
|
|
149
157
|
_id: false,
|
|
150
158
|
timestamp: { type: Date, required: true, default: Date.now },
|
|
@@ -156,6 +164,8 @@ module.exports = function(mongoose, config) {
|
|
|
156
164
|
value: { type: Number },
|
|
157
165
|
breakdown: { type: Schema.Types.Mixed },
|
|
158
166
|
calculation: { type: String, trim: true },
|
|
167
|
+
currency: { type: String, trim: true },
|
|
168
|
+
scale: { type: Number, enum: [1, 1000, 1000000, 1000000000, 1000000000000] },
|
|
159
169
|
sources: [{
|
|
160
170
|
_id: false,
|
|
161
171
|
model: { type: String, enum: ['Document', 'Meeting', 'Message', 'Note'] },
|
|
@@ -443,6 +453,8 @@ module.exports = function(mongoose, config) {
|
|
|
443
453
|
value: currentMetric.value != null ? currentMetric.value : null,
|
|
444
454
|
breakdown: currentMetric.breakdown != null ? currentMetric.breakdown : null,
|
|
445
455
|
calculation: currentMetric.calculation || null,
|
|
456
|
+
currency: currentMetric.currency != null ? currentMetric.currency : null,
|
|
457
|
+
scale: currentMetric.scale != null ? currentMetric.scale : null,
|
|
446
458
|
sources: currentMetric.sources || [],
|
|
447
459
|
verified: currentMetric.verified != null ? currentMetric.verified : null,
|
|
448
460
|
history: currentMetric.history || []
|
|
@@ -459,6 +471,11 @@ module.exports = function(mongoose, config) {
|
|
|
459
471
|
'snapshots.$.metrics.$[metric].verified': metric.verified
|
|
460
472
|
};
|
|
461
473
|
|
|
474
|
+
// Persist per-metric currency/scale only when the caller actually supplies a value, so a
|
|
475
|
+
// caller that omits them (or passes null) never clobbers an already-migrated metric.
|
|
476
|
+
if (metric.currency != null) { $set['snapshots.$.metrics.$[metric].currency'] = metric.currency; }
|
|
477
|
+
if (metric.scale != null) { $set['snapshots.$.metrics.$[metric].scale'] = metric.scale; }
|
|
478
|
+
|
|
462
479
|
// Save previous state if the metric already exists
|
|
463
480
|
if (previousState) {
|
|
464
481
|
$set['snapshots.$.metrics.$[metric].previous'] = previousState;
|
|
@@ -564,7 +581,7 @@ module.exports = function(mongoose, config) {
|
|
|
564
581
|
const $set = {};
|
|
565
582
|
const $unset = { [prefix + '.previous']: 1 };
|
|
566
583
|
|
|
567
|
-
const fields = ['value', 'breakdown', 'calculation', 'sources', 'verified'];
|
|
584
|
+
const fields = ['value', 'breakdown', 'calculation', 'currency', 'scale', 'sources', 'verified'];
|
|
568
585
|
fields.forEach(field => {
|
|
569
586
|
if (prev[field] != null) {
|
|
570
587
|
$set[prefix + '.' + field] = prev[field];
|
package/package.json
CHANGED
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
* 10. Link metrics to their source financial statement documents (Income Statement,
|
|
22
22
|
* Balance Sheet, Cash Flow Statement) when available
|
|
23
23
|
* 11. Remove projections array from root level
|
|
24
|
+
* 12. Backfill per-metric currency/scale from the (now-deprecated) snapshot-level
|
|
25
|
+
* currency/scale. Idempotent: metrics that already carry their own are left untouched.
|
|
26
|
+
* Defaults to 'USD' / 1 when the snapshot has neither. Part of the snapshot→metric
|
|
27
|
+
* currency migration (dual-write transition; snapshot fields retained as fallback).
|
|
24
28
|
*
|
|
25
29
|
* Usage:
|
|
26
30
|
* node scripts/migrate-financials-schema.js <customerId> [--config <configPath>] [--dry-run]
|
|
@@ -81,6 +85,14 @@ if (configPath) {
|
|
|
81
85
|
dbUri = config.db.uri;
|
|
82
86
|
}
|
|
83
87
|
|
|
88
|
+
// Valid metric scales (must match the enum in lib/Financials.js). The backfill sanitizes the
|
|
89
|
+
// snapshot's stored scale against this set so a corrupt legacy value can't propagate into a
|
|
90
|
+
// metric and fail a future validated write. Unknown/absent values fall back to 1.
|
|
91
|
+
const VALID_METRIC_SCALES = [1, 1000, 1000000, 1000000000, 1000000000000];
|
|
92
|
+
function sanitizeScale(scale) {
|
|
93
|
+
return VALID_METRIC_SCALES.includes(scale) ? scale : 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
84
96
|
console.log('Migration Configuration:');
|
|
85
97
|
console.log(' Customer ID:', CUSTOMER_ID);
|
|
86
98
|
console.log(' Database URI:', dbUri.replace(/\/\/[^:]+:[^@]+@/, '//*****:*****@')); // Mask credentials
|
|
@@ -312,12 +324,18 @@ async function migrateFinancials() {
|
|
|
312
324
|
const source = getSourceForMetric(metric.name);
|
|
313
325
|
|
|
314
326
|
// Create separate metric entries for each scenario that has a value
|
|
327
|
+
// Backfill per-metric currency/scale from the (now-deprecated) snapshot-level values
|
|
328
|
+
const metricCurrency = snapshot.currency || 'USD';
|
|
329
|
+
const metricScale = sanitizeScale(snapshot.scale);
|
|
330
|
+
|
|
315
331
|
if ('actual' in metric && metric.actual !== undefined && metric.actual !== null) {
|
|
316
332
|
const newMetric = {
|
|
317
333
|
name: metric.name,
|
|
318
334
|
scenario: 'actual',
|
|
319
335
|
value: metric.actual,
|
|
320
336
|
breakdown: metric.breakdown || {},
|
|
337
|
+
currency: metricCurrency,
|
|
338
|
+
scale: metricScale,
|
|
321
339
|
verified: true,
|
|
322
340
|
history: historyArrayForMetrics
|
|
323
341
|
};
|
|
@@ -331,6 +349,8 @@ async function migrateFinancials() {
|
|
|
331
349
|
scenario: 'budget',
|
|
332
350
|
value: metric.budget,
|
|
333
351
|
breakdown: {},
|
|
352
|
+
currency: metricCurrency,
|
|
353
|
+
scale: metricScale,
|
|
334
354
|
verified: true,
|
|
335
355
|
history: historyArrayForMetrics
|
|
336
356
|
};
|
|
@@ -344,6 +364,8 @@ async function migrateFinancials() {
|
|
|
344
364
|
scenario: 'forecast',
|
|
345
365
|
value: metric.forecast,
|
|
346
366
|
breakdown: {},
|
|
367
|
+
currency: metricCurrency,
|
|
368
|
+
scale: metricScale,
|
|
347
369
|
verified: true,
|
|
348
370
|
history: historyArrayForMetrics
|
|
349
371
|
};
|
|
@@ -356,7 +378,7 @@ async function migrateFinancials() {
|
|
|
356
378
|
updates[`snapshots.${index}.metrics`] = newMetrics;
|
|
357
379
|
changes.push(` - Snapshot ${index}: Converted ${oldMetrics.length} metric(s) to new structure (${newMetrics.length} entries, ${metricsWithSources} with sources)`);
|
|
358
380
|
} else {
|
|
359
|
-
// Already in new structure, just add verified, history, and
|
|
381
|
+
// Already in new structure, just add verified, history, source, and currency/scale if missing
|
|
360
382
|
oldMetrics.forEach((metric, mIndex) => {
|
|
361
383
|
if (!('verified' in metric)) {
|
|
362
384
|
updates[`snapshots.${index}.metrics.${mIndex}.verified`] = true;
|
|
@@ -373,6 +395,17 @@ async function migrateFinancials() {
|
|
|
373
395
|
changes.push(` - Snapshot ${index}: Added sources to metric ${mIndex} (${metric.name})`);
|
|
374
396
|
}
|
|
375
397
|
}
|
|
398
|
+
// Backfill per-metric currency/scale from the (now-deprecated) snapshot-level values.
|
|
399
|
+
// Idempotent: only set when the metric doesn't already carry its own.
|
|
400
|
+
if (!('currency' in metric)) {
|
|
401
|
+
updates[`snapshots.${index}.metrics.${mIndex}.currency`] = snapshot.currency || 'USD';
|
|
402
|
+
changes.push(` - Snapshot ${index}: Backfilled currency (${snapshot.currency || 'USD'}) to metric ${mIndex} (${metric.name})`);
|
|
403
|
+
}
|
|
404
|
+
if (!('scale' in metric)) {
|
|
405
|
+
const backfillScale = sanitizeScale(snapshot.scale);
|
|
406
|
+
updates[`snapshots.${index}.metrics.${mIndex}.scale`] = backfillScale;
|
|
407
|
+
changes.push(` - Snapshot ${index}: Backfilled scale (${backfillScale}) to metric ${mIndex} (${metric.name})`);
|
|
408
|
+
}
|
|
376
409
|
});
|
|
377
410
|
}
|
|
378
411
|
}
|