@dhyasama/totem-models 11.114.0 → 11.115.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 +16 -3
- package/package.json +1 -1
- package/scripts/migrate-financials-schema.js +279 -0
package/lib/Financials.js
CHANGED
|
@@ -98,6 +98,11 @@ module.exports = function(mongoose, config) {
|
|
|
98
98
|
name: { type: String, trim: true },
|
|
99
99
|
document: { type: Schema.ObjectId, ref: 'Document' },
|
|
100
100
|
verified: { type: Boolean, default: false },
|
|
101
|
+
history: [{
|
|
102
|
+
timestamp: { type: Date, required: true, default: Date.now },
|
|
103
|
+
user: { type: String, trim: true, required: true },
|
|
104
|
+
description: { type: String, trim: true, required: true }
|
|
105
|
+
}]
|
|
101
106
|
}],
|
|
102
107
|
|
|
103
108
|
metrics: [{
|
|
@@ -110,16 +115,19 @@ module.exports = function(mongoose, config) {
|
|
|
110
115
|
_id: false,
|
|
111
116
|
model: {
|
|
112
117
|
type: String,
|
|
113
|
-
enum: ['Document', 'Meeting', 'Message', 'Note']
|
|
114
|
-
required: true,
|
|
118
|
+
enum: ['Document', 'Meeting', 'Message', 'Note']
|
|
115
119
|
},
|
|
116
120
|
ref: {
|
|
117
121
|
type: Schema.Types.ObjectId,
|
|
118
|
-
required: true,
|
|
119
122
|
refPath: 'metrics.source.model',
|
|
120
123
|
},
|
|
121
124
|
},
|
|
122
125
|
verified: { type: Boolean, default: false },
|
|
126
|
+
history: [{
|
|
127
|
+
timestamp: { type: Date, required: true, default: Date.now },
|
|
128
|
+
user: { type: String, trim: true, required: true },
|
|
129
|
+
description: { type: String, trim: true, required: true }
|
|
130
|
+
}]
|
|
123
131
|
}],
|
|
124
132
|
|
|
125
133
|
questions: [{
|
|
@@ -127,6 +135,11 @@ module.exports = function(mongoose, config) {
|
|
|
127
135
|
prompt: { type: String, trim: true },
|
|
128
136
|
response: { type: String, trim: true },
|
|
129
137
|
verified: { type: Boolean, default: false },
|
|
138
|
+
history: [{
|
|
139
|
+
timestamp: { type: Date, required: true, default: Date.now },
|
|
140
|
+
user: { type: String, trim: true, required: true },
|
|
141
|
+
description: { type: String, trim: true, required: true }
|
|
142
|
+
}]
|
|
130
143
|
}],
|
|
131
144
|
|
|
132
145
|
dueOn: { type: Date, index: false },
|
package/package.json
CHANGED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Migration script for Financials model schema changes
|
|
5
|
+
*
|
|
6
|
+
* Changes being migrated:
|
|
7
|
+
* 1. Rename field: review → approval (in recurring and snapshots)
|
|
8
|
+
* 2. Update status enum values in snapshots:
|
|
9
|
+
* - 'Overdue' → 'Delayed'
|
|
10
|
+
* - 'In Review' → 'Unapproved'
|
|
11
|
+
* 3. Add verified: true to all existing documents[], metrics[], questions[]
|
|
12
|
+
* 4. Convert metrics from actual/budget/forecast fields to scenario/value structure
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* node scripts/migrate-financials-schema.js <customerId> [--config <configPath>] [--dry-run]
|
|
16
|
+
* node scripts/migrate-financials-schema.js <customerId> [--db-uri <mongoUri>] [--dry-run]
|
|
17
|
+
*
|
|
18
|
+
* Examples:
|
|
19
|
+
* # Use totem-web production config
|
|
20
|
+
* NODE_ENV=production node scripts/migrate-financials-schema.js 5cdc3340e67922775af918ca --config ../totem-web/config/config.js --dry-run
|
|
21
|
+
*
|
|
22
|
+
* # Use DB_URI environment variable
|
|
23
|
+
* DB_URI="mongodb://..." node scripts/migrate-financials-schema.js 5cdc3340e67922775af918ca --dry-run
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const mongoose = require('mongoose');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
|
|
29
|
+
// Parse command line arguments
|
|
30
|
+
const args = process.argv.slice(2);
|
|
31
|
+
const CUSTOMER_ID = args[0];
|
|
32
|
+
const DRY_RUN = args.includes('--dry-run');
|
|
33
|
+
|
|
34
|
+
// Get config path or db uri
|
|
35
|
+
let configPath = null;
|
|
36
|
+
let dbUri = null;
|
|
37
|
+
|
|
38
|
+
const configIndex = args.indexOf('--config');
|
|
39
|
+
if (configIndex !== -1 && args[configIndex + 1]) {
|
|
40
|
+
configPath = path.resolve(args[configIndex + 1]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const dbUriIndex = args.indexOf('--db-uri');
|
|
44
|
+
if (dbUriIndex !== -1 && args[dbUriIndex + 1]) {
|
|
45
|
+
dbUri = args[dbUriIndex + 1];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check for DB_URI environment variable
|
|
49
|
+
if (!dbUri && process.env.DB_URI) {
|
|
50
|
+
dbUri = process.env.DB_URI;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Validate arguments
|
|
54
|
+
if (!CUSTOMER_ID || CUSTOMER_ID.startsWith('--')) {
|
|
55
|
+
console.error('Error: Customer ID is required');
|
|
56
|
+
console.error('Usage: node scripts/migrate-financials-schema.js <customerId> [--config <configPath>] [--db-uri <mongoUri>] [--dry-run]');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!configPath && !dbUri) {
|
|
61
|
+
console.error('Error: Either --config, --db-uri, or DB_URI environment variable is required');
|
|
62
|
+
console.error('Usage: node scripts/migrate-financials-schema.js <customerId> [--config <configPath>] [--db-uri <mongoUri>] [--dry-run]');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Load config if provided
|
|
67
|
+
if (configPath) {
|
|
68
|
+
console.log(`Loading config from: ${configPath}`);
|
|
69
|
+
const config = require(configPath);
|
|
70
|
+
dbUri = config.db.uri;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log('Migration Configuration:');
|
|
74
|
+
console.log(' Customer ID:', CUSTOMER_ID);
|
|
75
|
+
console.log(' Database URI:', dbUri.replace(/\/\/[^:]+:[^@]+@/, '//*****:*****@')); // Mask credentials
|
|
76
|
+
console.log(' Dry Run:', DRY_RUN);
|
|
77
|
+
console.log('');
|
|
78
|
+
|
|
79
|
+
// Connect to MongoDB
|
|
80
|
+
const mongooseOptions = {
|
|
81
|
+
useNewUrlParser: true,
|
|
82
|
+
useUnifiedTopology: true
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
mongoose.connect(dbUri, mongooseOptions);
|
|
86
|
+
|
|
87
|
+
const db = mongoose.connection;
|
|
88
|
+
|
|
89
|
+
db.on('error', (err) => {
|
|
90
|
+
console.error('MongoDB connection error:', err);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
db.once('open', async () => {
|
|
95
|
+
console.log('Connected to MongoDB');
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await migrateFinancials();
|
|
99
|
+
console.log('\nMigration completed successfully!');
|
|
100
|
+
process.exit(0);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error('\nMigration failed:', err);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
async function migrateFinancials() {
|
|
108
|
+
const Financials = db.collection('financials');
|
|
109
|
+
|
|
110
|
+
// Find all financials for this customer
|
|
111
|
+
const query = { customer: mongoose.Types.ObjectId(CUSTOMER_ID) };
|
|
112
|
+
const financials = await Financials.find(query).toArray();
|
|
113
|
+
|
|
114
|
+
console.log(`Found ${financials.length} financial document(s) for customer ${CUSTOMER_ID}\n`);
|
|
115
|
+
|
|
116
|
+
if (financials.length === 0) {
|
|
117
|
+
console.log('No documents to migrate.');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let migratedCount = 0;
|
|
122
|
+
let unchangedCount = 0;
|
|
123
|
+
|
|
124
|
+
for (const financial of financials) {
|
|
125
|
+
const updates = {};
|
|
126
|
+
const changes = [];
|
|
127
|
+
|
|
128
|
+
// 1. Migrate recurring.review → recurring.approval
|
|
129
|
+
if (financial.recurring && 'review' in financial.recurring) {
|
|
130
|
+
updates['recurring.approval'] = financial.recurring.review;
|
|
131
|
+
updates.$unset = updates.$unset || {};
|
|
132
|
+
updates.$unset['recurring.review'] = '';
|
|
133
|
+
changes.push(' - Renamed recurring.review → recurring.approval');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 2. Migrate snapshots
|
|
137
|
+
if (financial.snapshots && financial.snapshots.length > 0) {
|
|
138
|
+
financial.snapshots.forEach((snapshot, index) => {
|
|
139
|
+
|
|
140
|
+
// 2a. Rename snapshot.review → snapshot.approval
|
|
141
|
+
if ('review' in snapshot) {
|
|
142
|
+
updates[`snapshots.${index}.approval`] = snapshot.review;
|
|
143
|
+
updates.$unset = updates.$unset || {};
|
|
144
|
+
updates.$unset[`snapshots.${index}.review`] = '';
|
|
145
|
+
changes.push(` - Snapshot ${index}: Renamed review → approval`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 2b. Update status enum values
|
|
149
|
+
if (snapshot.status === 'Overdue') {
|
|
150
|
+
updates[`snapshots.${index}.status`] = 'Delayed';
|
|
151
|
+
changes.push(` - Snapshot ${index}: Changed status 'Overdue' → 'Delayed'`);
|
|
152
|
+
} else if (snapshot.status === 'In Review') {
|
|
153
|
+
updates[`snapshots.${index}.status`] = 'Unapproved';
|
|
154
|
+
changes.push(` - Snapshot ${index}: Changed status 'In Review' → 'Unapproved'`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 2c. Add verified: true to documents
|
|
158
|
+
if (snapshot.documents && snapshot.documents.length > 0) {
|
|
159
|
+
snapshot.documents.forEach((doc, docIndex) => {
|
|
160
|
+
if (!('verified' in doc)) {
|
|
161
|
+
updates[`snapshots.${index}.documents.${docIndex}.verified`] = true;
|
|
162
|
+
changes.push(` - Snapshot ${index}: Added verified to document ${docIndex}`);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 2d. Add verified: true to questions
|
|
168
|
+
if (snapshot.questions && snapshot.questions.length > 0) {
|
|
169
|
+
snapshot.questions.forEach((question, qIndex) => {
|
|
170
|
+
if (!('verified' in question)) {
|
|
171
|
+
updates[`snapshots.${index}.questions.${qIndex}.verified`] = true;
|
|
172
|
+
changes.push(` - Snapshot ${index}: Added verified to question ${qIndex}`);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 2e. Migrate metrics structure and add verified
|
|
178
|
+
if (snapshot.metrics && snapshot.metrics.length > 0) {
|
|
179
|
+
const oldMetrics = [...snapshot.metrics];
|
|
180
|
+
const newMetrics = [];
|
|
181
|
+
|
|
182
|
+
// Check if metrics need conversion from old structure
|
|
183
|
+
const needsConversion = oldMetrics.some(m =>
|
|
184
|
+
('actual' in m || 'budget' in m || 'forecast' in m) && !('scenario' in m)
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
if (needsConversion) {
|
|
188
|
+
// Convert old structure (actual/budget/forecast) to new structure (scenario/value)
|
|
189
|
+
oldMetrics.forEach((metric) => {
|
|
190
|
+
// Create separate metric entries for each scenario
|
|
191
|
+
if ('actual' in metric && metric.actual !== undefined && metric.actual !== null) {
|
|
192
|
+
newMetrics.push({
|
|
193
|
+
name: metric.name,
|
|
194
|
+
scenario: 'actual',
|
|
195
|
+
value: metric.actual,
|
|
196
|
+
breakdown: metric.breakdown,
|
|
197
|
+
verified: true
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if ('budget' in metric && metric.budget !== undefined && metric.budget !== null) {
|
|
202
|
+
newMetrics.push({
|
|
203
|
+
name: metric.name,
|
|
204
|
+
scenario: 'budget',
|
|
205
|
+
value: metric.budget,
|
|
206
|
+
breakdown: metric.breakdown,
|
|
207
|
+
verified: true
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if ('forecast' in metric && metric.forecast !== undefined && metric.forecast !== null) {
|
|
212
|
+
newMetrics.push({
|
|
213
|
+
name: metric.name,
|
|
214
|
+
scenario: 'forecast',
|
|
215
|
+
value: metric.forecast,
|
|
216
|
+
breakdown: metric.breakdown,
|
|
217
|
+
verified: true
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
updates[`snapshots.${index}.metrics`] = newMetrics;
|
|
223
|
+
changes.push(` - Snapshot ${index}: Converted ${oldMetrics.length} metric(s) to new structure (${newMetrics.length} entries)`);
|
|
224
|
+
} else {
|
|
225
|
+
// Already in new structure, just add verified if missing
|
|
226
|
+
oldMetrics.forEach((metric, mIndex) => {
|
|
227
|
+
if (!('verified' in metric)) {
|
|
228
|
+
updates[`snapshots.${index}.metrics.${mIndex}.verified`] = true;
|
|
229
|
+
changes.push(` - Snapshot ${index}: Added verified to metric ${mIndex}`);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Apply updates
|
|
238
|
+
if (Object.keys(updates).length > 0 && !DRY_RUN) {
|
|
239
|
+
const updateDoc = {};
|
|
240
|
+
|
|
241
|
+
// Separate $set and $unset operations
|
|
242
|
+
if (updates.$unset) {
|
|
243
|
+
updateDoc.$unset = updates.$unset;
|
|
244
|
+
delete updates.$unset;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (Object.keys(updates).length > 0) {
|
|
248
|
+
updateDoc.$set = updates;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
await Financials.updateOne(
|
|
252
|
+
{ _id: financial._id },
|
|
253
|
+
updateDoc
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
console.log(`Migrated financial ${financial._id}:`);
|
|
257
|
+
changes.forEach(change => console.log(change));
|
|
258
|
+
console.log('');
|
|
259
|
+
migratedCount++;
|
|
260
|
+
} else if (Object.keys(updates).length > 0 && DRY_RUN) {
|
|
261
|
+
console.log(`[DRY RUN] Would migrate financial ${financial._id}:`);
|
|
262
|
+
changes.forEach(change => console.log(change));
|
|
263
|
+
console.log('');
|
|
264
|
+
migratedCount++;
|
|
265
|
+
} else {
|
|
266
|
+
unchangedCount++;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
console.log('\n=== Migration Summary ===');
|
|
271
|
+
console.log(`Documents migrated: ${migratedCount}`);
|
|
272
|
+
console.log(`Documents unchanged: ${unchangedCount}`);
|
|
273
|
+
console.log(`Total documents: ${financials.length}`);
|
|
274
|
+
|
|
275
|
+
if (DRY_RUN) {
|
|
276
|
+
console.log('\nThis was a dry run. No changes were made to the database.');
|
|
277
|
+
console.log('Run without --dry-run to apply changes.');
|
|
278
|
+
}
|
|
279
|
+
}
|