@dhyasama/totem-models 1.0.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/.npmignore +14 -0
- package/changeBSON.js +27 -0
- package/dictionaries/nicknames.json +703 -0
- package/index.js +42 -0
- package/lib/Account.js +312 -0
- package/lib/Activity.js +138 -0
- package/lib/CalendarEvent.js +150 -0
- package/lib/Document.js +357 -0
- package/lib/Fund.js +143 -0
- package/lib/LimitedPartner.js +780 -0
- package/lib/List.js +217 -0
- package/lib/News.js +161 -0
- package/lib/Note.js +170 -0
- package/lib/Organization.js +1011 -0
- package/lib/Person.js +1163 -0
- package/package.json +37 -0
- package/test/Account.js +139 -0
- package/test/Activity.js +167 -0
- package/test/CalendarEvent.js +59 -0
- package/test/Fund.js +113 -0
- package/test/LimitedPartner.js +554 -0
- package/test/List.js +270 -0
- package/test/News.js +109 -0
- package/test/Note.js +304 -0
- package/test/Organization.js +750 -0
- package/test/Person.js +1042 -0
- package/test/config.js +49 -0
package/lib/Document.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = function(mongoose, config) {
|
|
4
|
+
|
|
5
|
+
var
|
|
6
|
+
|
|
7
|
+
Schema = mongoose.Schema,
|
|
8
|
+
env = process.env.NODE_ENV || 'development',
|
|
9
|
+
s3 = require('@dhyasama/ffvc-s3'),
|
|
10
|
+
utils = require('@dhyasama/ffvc-utilities'),
|
|
11
|
+
Crypto = require('@dhyasama/ffvc-crypto'),
|
|
12
|
+
crypto = new Crypto({'key': config.crypto.key}),
|
|
13
|
+
_ = require('underscore');
|
|
14
|
+
|
|
15
|
+
var Document = new Schema({
|
|
16
|
+
|
|
17
|
+
lpid: { type: Schema.Types.ObjectId, required: true },
|
|
18
|
+
|
|
19
|
+
fundInvolved: {
|
|
20
|
+
type: Schema.ObjectId,
|
|
21
|
+
ref: 'Fund'
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
published: { type: Boolean, required: true },
|
|
25
|
+
|
|
26
|
+
filename: { type: String, required: true },
|
|
27
|
+
|
|
28
|
+
filetype: { type: String, required: true },
|
|
29
|
+
|
|
30
|
+
filesize: { type: String, required: true },
|
|
31
|
+
|
|
32
|
+
s3key: { type: String, required: true },
|
|
33
|
+
|
|
34
|
+
doctype: {
|
|
35
|
+
type: String,
|
|
36
|
+
required: true,
|
|
37
|
+
enum: ['annual-audit', 'portfolio-holding', 'capital-call', 'k1', 'distribution', 'quarterly-account-statement'] },
|
|
38
|
+
|
|
39
|
+
scope: {
|
|
40
|
+
type: String,
|
|
41
|
+
required: true,
|
|
42
|
+
enum: ['lp', 'fund'] },
|
|
43
|
+
|
|
44
|
+
// for fund-level docs:
|
|
45
|
+
// upload one doc to s3 with this id in the key
|
|
46
|
+
// e.g., an annual audit for ff Rose would be stored at documents/[scopeid] and shared by all lps in ff Rose
|
|
47
|
+
// for lp-level docs:
|
|
48
|
+
// used to get a batch of docs that were uploaded together
|
|
49
|
+
// e.g., a capital call for ff Sapphire requires a separate document to be uploaded for each lp in the fund
|
|
50
|
+
// scopeid allows retrieving all of the doc records at once for admin purposes
|
|
51
|
+
// unlike fund level docs, these are stored at documents/[_id] so they can be retrieved on an individual basis
|
|
52
|
+
scopeid: { type: String, required: true },
|
|
53
|
+
|
|
54
|
+
year: { type: Number, required: false },
|
|
55
|
+
|
|
56
|
+
quarter: {
|
|
57
|
+
type: String,
|
|
58
|
+
required: false,
|
|
59
|
+
enum: [null, '', 'Q1', 'Q2', 'Q3', 'Q4'] },
|
|
60
|
+
|
|
61
|
+
capitalCall: {
|
|
62
|
+
noticeNumber: { type: Number, required: false },
|
|
63
|
+
noticeDate: { type: Date, required: false },
|
|
64
|
+
dueDate: { type: Date, required: false }
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
description: { type: String, required: false },
|
|
68
|
+
|
|
69
|
+
history: [{
|
|
70
|
+
timestamp: { type: Date, required: true },
|
|
71
|
+
username: { type: String, required: true },
|
|
72
|
+
action: { type: String, required: true, enum: ['created', 'published', 'unpublished', 'replaced'] }
|
|
73
|
+
}]
|
|
74
|
+
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
/////////////////////
|
|
80
|
+
// INSTANCE METHODS
|
|
81
|
+
/////////////////////
|
|
82
|
+
|
|
83
|
+
Document.methods.friendlyFundName = function() {
|
|
84
|
+
var fund = this.fundInvolved.name;
|
|
85
|
+
return fund;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
Document.methods.friendlyDocType = function() {
|
|
89
|
+
var doctype = this.doctype ? utils.capitalizeFirstLetters(this.doctype.toLowerCase().split('-').join(' ')) : '';
|
|
90
|
+
return doctype;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
Document.methods.friendlyDocumentName = function() {
|
|
94
|
+
var year = this.year ? this.year + ' ' : '';
|
|
95
|
+
var quarter = this.quarter ? this.quarter + ' ' : '';
|
|
96
|
+
var callNum = this.doctype == 'capital-call' && utils.isNumeric(this.capitalCall.noticeNumber) ? ' #' + this.capitalCall.noticeNumber : '';
|
|
97
|
+
var desc = this.doctype == 'distribution' ? ' - ' + this.description : '';
|
|
98
|
+
|
|
99
|
+
return year + quarter + this.friendlyDocType() + callNum + desc;
|
|
100
|
+
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
Document.methods.friendlyFilesize = function() {
|
|
104
|
+
return utils.humanizeFilesize(this.filesize);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
Document.methods.encryptId = function() {
|
|
108
|
+
return crypto.encrypt(this.id);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
////////////////////////
|
|
114
|
+
// STATICS
|
|
115
|
+
////////////////////////
|
|
116
|
+
|
|
117
|
+
Document.statics.exists = function(meta, cb) {
|
|
118
|
+
|
|
119
|
+
var model = this;
|
|
120
|
+
|
|
121
|
+
var done = function(err, docs) {
|
|
122
|
+
return cb(!err && docs.length >= 1, docs[0]);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
switch(meta.doctype.toLowerCase()) {
|
|
126
|
+
case 'annual-audit':
|
|
127
|
+
listAnnualAudits(model, meta.fundId, meta.year, done);
|
|
128
|
+
break;
|
|
129
|
+
case 'capital-call':
|
|
130
|
+
listCapitalCalls(model, meta.fundId, meta.callNumber, done);
|
|
131
|
+
break;
|
|
132
|
+
case 'portfolio-holding':
|
|
133
|
+
listPortfolioHoldings(model, meta.fundId, meta.year, meta.quarter, done);
|
|
134
|
+
break;
|
|
135
|
+
case 'k1':
|
|
136
|
+
listK1s(model, meta.fundId, meta.year, done);
|
|
137
|
+
break;
|
|
138
|
+
case 'quarterly-account-statement':
|
|
139
|
+
listQuarterlyAccountStatements(model, meta.fundId, meta.year, meta.quarter, done);
|
|
140
|
+
break;
|
|
141
|
+
default:
|
|
142
|
+
return cb(false, null);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
Document.statics.getById = function (cipher, cb) {
|
|
149
|
+
|
|
150
|
+
var id = crypto.decrypt(cipher);
|
|
151
|
+
|
|
152
|
+
this
|
|
153
|
+
.findById(id)
|
|
154
|
+
.populate({
|
|
155
|
+
path: 'lpid',
|
|
156
|
+
model: 'LimitedPartner',
|
|
157
|
+
select: 'name shortName id'
|
|
158
|
+
})
|
|
159
|
+
.populate('fundInvolved')
|
|
160
|
+
.exec(function (err, document) {
|
|
161
|
+
if (document) { document.secureid = cipher; }
|
|
162
|
+
document.secureid = cipher;
|
|
163
|
+
cb(err, document);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
Document.statics.list = function (cb) {
|
|
169
|
+
|
|
170
|
+
this
|
|
171
|
+
.find()
|
|
172
|
+
.populate({
|
|
173
|
+
path: 'lpid',
|
|
174
|
+
model: 'LimitedPartner',
|
|
175
|
+
select: 'name shortName id'
|
|
176
|
+
})
|
|
177
|
+
.populate('fundInvolved')
|
|
178
|
+
.sort({ year: -1, quarter: -1 })
|
|
179
|
+
.exec(function (err, documents) {
|
|
180
|
+
encryptDocumentIds(documents);
|
|
181
|
+
cb(err, documents);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
Document.statics.listAllForLP = function (lpid, cb) {
|
|
187
|
+
|
|
188
|
+
this
|
|
189
|
+
.find({ lpid: lpid, published: true })
|
|
190
|
+
.populate('fundInvolved')
|
|
191
|
+
.sort({ year: -1, quarter: -1, fund: 1 })
|
|
192
|
+
.exec(function (err, documents) {
|
|
193
|
+
encryptDocumentIds(documents);
|
|
194
|
+
return cb(err, documents);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
Document.statics.listForScopeId = function (cipher, cb) {
|
|
200
|
+
|
|
201
|
+
// lp-level docs are grouped to scope id to faciliate pulling a batch all at once
|
|
202
|
+
|
|
203
|
+
var id = crypto.decrypt(cipher);
|
|
204
|
+
|
|
205
|
+
this
|
|
206
|
+
.find({ 'scopeid': id })
|
|
207
|
+
.populate({
|
|
208
|
+
path: 'lpid',
|
|
209
|
+
model: 'LimitedPartner',
|
|
210
|
+
select: 'name shortName id'
|
|
211
|
+
})
|
|
212
|
+
.populate('fundInvolved')
|
|
213
|
+
.exec(function (err, documents) {
|
|
214
|
+
console.log(documents.length);
|
|
215
|
+
encryptDocumentIds(documents);
|
|
216
|
+
return cb(err, _.sortBy(documents, function(doc) {
|
|
217
|
+
var lowered = doc.lpid && doc.lpid.name ? doc.lpid.name.toLowerCase() : 'Unknown';
|
|
218
|
+
return lowered;
|
|
219
|
+
}));
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
Document.statics.listFundLevel = function (cb) {
|
|
225
|
+
|
|
226
|
+
this
|
|
227
|
+
.find({ 'scope': 'fund' })
|
|
228
|
+
.populate({
|
|
229
|
+
path: 'lpid',
|
|
230
|
+
model: 'LimitedPartner',
|
|
231
|
+
select: 'name shortName id'
|
|
232
|
+
})
|
|
233
|
+
.populate('fundInvolved')
|
|
234
|
+
.sort({ year: -1, quarter: -1 })
|
|
235
|
+
.exec(function (err, documents) {
|
|
236
|
+
var docs = _.uniq(documents, false, function(doc) {
|
|
237
|
+
return doc.scopeid;
|
|
238
|
+
});
|
|
239
|
+
encryptDocumentIds(docs);
|
|
240
|
+
cb(err, docs);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
Document.statics.listLpLevel = function (cb) {
|
|
246
|
+
|
|
247
|
+
this
|
|
248
|
+
.find({ 'scope': 'lp' })
|
|
249
|
+
.populate({
|
|
250
|
+
path: 'lpid',
|
|
251
|
+
model: 'LimitedPartner',
|
|
252
|
+
select: 'name shortName id'
|
|
253
|
+
})
|
|
254
|
+
.populate('fundInvolved')
|
|
255
|
+
.sort({ year: -1 })
|
|
256
|
+
.exec(function (err, documents) {
|
|
257
|
+
var docs = _.uniq(documents, false, function(doc) {
|
|
258
|
+
return doc.scopeid;
|
|
259
|
+
});
|
|
260
|
+
encryptDocumentIds(docs);
|
|
261
|
+
cb(err, docs);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
Document.statics.upsert = function(doc, cb) {
|
|
267
|
+
return save(doc, cb);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
////////////////////////
|
|
273
|
+
// HELPERS
|
|
274
|
+
////////////////////////
|
|
275
|
+
|
|
276
|
+
function encryptDocumentIds(documents) {
|
|
277
|
+
|
|
278
|
+
var result = _.map(documents, function(doc) {
|
|
279
|
+
doc.secureid = crypto.encrypt(doc.id);
|
|
280
|
+
return doc;
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
return result;
|
|
284
|
+
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function listAnnualAudits(model, fundId, year, cb) {
|
|
288
|
+
|
|
289
|
+
model
|
|
290
|
+
.find({ doctype: 'annual-audit', fundInvolved: fundId, year: year })
|
|
291
|
+
.sort({ year: -1 })
|
|
292
|
+
.exec(function (err, documents) {
|
|
293
|
+
encryptDocumentIds(documents);
|
|
294
|
+
cb(err, documents);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
function listCapitalCalls(model, fundId, callNumber, cb) {
|
|
300
|
+
|
|
301
|
+
model
|
|
302
|
+
.find({ doctype: 'capital-call', fundInvolved: fundId, 'capitalCall.noticeNumber': callNumber })
|
|
303
|
+
.sort({ 'capitalCall.noticeNumber': -1 })
|
|
304
|
+
.exec(function (err, documents) {
|
|
305
|
+
encryptDocumentIds(documents);
|
|
306
|
+
cb(err, documents);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
function listK1s(model, fundId, year, cb) {
|
|
312
|
+
|
|
313
|
+
model
|
|
314
|
+
.find({ doctype: 'k1', fundInvolved: fundId, year: year })
|
|
315
|
+
.sort({ 'year': -1 })
|
|
316
|
+
.exec(function (err, documents) {
|
|
317
|
+
encryptDocumentIds(documents);
|
|
318
|
+
cb(err, documents);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
function listPortfolioHoldings(model, fundId, year, quarter, cb) {
|
|
324
|
+
|
|
325
|
+
model
|
|
326
|
+
.find({ doctype: 'portfolio-holding', fundInvolved: fundId, year: year, quarter: quarter })
|
|
327
|
+
.sort({ year: -1, quarter: -1 })
|
|
328
|
+
.exec(function (err, documents) {
|
|
329
|
+
return cb(err, encryptDocumentIds(documents));
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
function listQuarterlyAccountStatements(model, fundId, year, quarter, cb) {
|
|
335
|
+
|
|
336
|
+
model
|
|
337
|
+
.find({ doctype: 'quarterly-account-statement', fundInvolved: fundId, year: year, quarter: quarter })
|
|
338
|
+
.sort({ year: -1, quarter: -1 })
|
|
339
|
+
.exec(function (err, documents) {
|
|
340
|
+
encryptDocumentIds(documents);
|
|
341
|
+
cb(err, documents);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
function save(document, cb) {
|
|
347
|
+
document.save(function(err, updatedDocument) {
|
|
348
|
+
cb(err, updatedDocument);
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
Document.set('autoIndex', (env == 'development'));
|
|
353
|
+
Document.on('index', function(err) { console.log('error building document indexes: ' + err); });
|
|
354
|
+
|
|
355
|
+
mongoose.model('Document', Document);
|
|
356
|
+
|
|
357
|
+
};
|
package/lib/Fund.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = function(mongoose, config) {
|
|
4
|
+
|
|
5
|
+
var Schema = mongoose.Schema
|
|
6
|
+
var env = process.env.NODE_ENV || 'development';
|
|
7
|
+
|
|
8
|
+
var Fund = new Schema({
|
|
9
|
+
|
|
10
|
+
name: { type: String, required: true },
|
|
11
|
+
slug: { type: String, required: true, unique: true },
|
|
12
|
+
shortName: { type: String, required: true },
|
|
13
|
+
spv: { type: Boolean, default: false },
|
|
14
|
+
hexColorCode: { type: String, required: true, default: '#cccccc' },
|
|
15
|
+
closeDate: { type: Date },
|
|
16
|
+
amount: { type: Number },
|
|
17
|
+
crunchbase: { uuid: { type: String, default: '', unique: true } }
|
|
18
|
+
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/************* BASIC FIELDS **********************/
|
|
22
|
+
|
|
23
|
+
Fund.methods.newBasic = function(field, value) {
|
|
24
|
+
|
|
25
|
+
var compoundField = false;
|
|
26
|
+
if(field.split(".").length > 1) {
|
|
27
|
+
compoundField = true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if(!compoundField) {
|
|
31
|
+
this[field] = value;
|
|
32
|
+
} else {
|
|
33
|
+
var parentField = field.split('.')[0];
|
|
34
|
+
var childField = field.split('.')[1];
|
|
35
|
+
|
|
36
|
+
this[parentField][childField] = value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
Fund.methods.updateBasic = function(field, value) {
|
|
42
|
+
|
|
43
|
+
var compoundField = false;
|
|
44
|
+
if(field.split(".").length > 1) {
|
|
45
|
+
compoundField = true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if(!compoundField) {
|
|
49
|
+
var currentVal = this[field];
|
|
50
|
+
this[field] = value;
|
|
51
|
+
} else {
|
|
52
|
+
var parentField = field.split('.')[0];
|
|
53
|
+
var childField = field.split('.')[1];
|
|
54
|
+
|
|
55
|
+
var currentVal = this[parentField][childField];
|
|
56
|
+
this[parentField][childField] = value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
var oldFieldObj = {
|
|
60
|
+
field: field,
|
|
61
|
+
value: currentVal,
|
|
62
|
+
end: new Date()
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
this.previous.push(oldFieldObj);
|
|
66
|
+
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
///////////////////////////////////////
|
|
70
|
+
|
|
71
|
+
Fund.statics.getById = function (id, cb) {
|
|
72
|
+
this
|
|
73
|
+
.findById(id)
|
|
74
|
+
.exec(cb);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
Fund.statics.getBySlug = function (slug, cb) {
|
|
78
|
+
this
|
|
79
|
+
.find({slug: slug})
|
|
80
|
+
.exec(cb);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
Fund.statics.findBySlugs = function (slugs, cb) {
|
|
84
|
+
this
|
|
85
|
+
.find({ 'slug': { $in : slugs }})
|
|
86
|
+
.exec(cb);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
Fund.statics.getCount = function (cb) {
|
|
90
|
+
this
|
|
91
|
+
.count({})
|
|
92
|
+
.exec(cb);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
Fund.statics.list = function (cb) {
|
|
96
|
+
this
|
|
97
|
+
.find()
|
|
98
|
+
.exec(cb);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
Fund.statics.search = function (query, options, cb) {
|
|
102
|
+
|
|
103
|
+
if (!cb) throw new Error('cb is required');
|
|
104
|
+
if (!query) return cb(null, []);
|
|
105
|
+
|
|
106
|
+
var conditions = {
|
|
107
|
+
'name': new RegExp(query.query, "i"),
|
|
108
|
+
'deleted': {$ne: true}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
this
|
|
112
|
+
.find(conditions)
|
|
113
|
+
.sort('name')
|
|
114
|
+
.exec(cb);
|
|
115
|
+
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
Fund.statics.upsert = function(fund, username, cb) {
|
|
119
|
+
|
|
120
|
+
if (!fund) { return cb(new Error('Fund is required'), null); }
|
|
121
|
+
if (!username) { return cb(new Error('Username is required'), null); }
|
|
122
|
+
|
|
123
|
+
fund.save(cb);
|
|
124
|
+
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
Fund.post('remove', function(doc) {
|
|
128
|
+
// Debt.debts.internal.fund
|
|
129
|
+
// Document.fundInvolved
|
|
130
|
+
// Investment.internal.fund
|
|
131
|
+
// Investor.funds
|
|
132
|
+
// LimitedPartner.fundsInvested.fund
|
|
133
|
+
// Message.fundsInvolved
|
|
134
|
+
// News.fundsInvolved
|
|
135
|
+
// PortfolioCompany.fundsInvested
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
Fund.set('autoIndex', true);
|
|
139
|
+
Fund.on('index', function(err) { console.log('error building fund indexes: ' + err); });
|
|
140
|
+
|
|
141
|
+
mongoose.model('Fund', Fund);
|
|
142
|
+
|
|
143
|
+
};
|