@dhyasama/totem-models 1.0.0 → 1.6.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.
@@ -2,8 +2,6 @@
2
2
 
3
3
  module.exports = function(mongoose, config) {
4
4
 
5
- var LPS_ENABLED = config.LPS_ENABLED || false;
6
-
7
5
  var
8
6
 
9
7
  _ = require('underscore'),
@@ -12,6 +10,8 @@ module.exports = function(mongoose, config) {
12
10
  Schema = mongoose.Schema,
13
11
  Note = mongoose.model('Note'),
14
12
 
13
+ customerPortfolio = [],
14
+
15
15
  Organization = new Schema({
16
16
 
17
17
  name: { type: String, required: true, index: true },
@@ -64,6 +64,7 @@ module.exports = function(mongoose, config) {
64
64
  title: { type: String, default: null, trim: true },
65
65
  current: { type: Boolean, default: false, index: true },
66
66
  leader: { type: Boolean, default: false, index: true },
67
+ public: { type: Boolean, default: true },
67
68
  board: { type: String, enum: ['chairman', 'director', 'observer', 'none'], default: 'none', index: true }
68
69
  }],
69
70
 
@@ -114,10 +115,6 @@ module.exports = function(mongoose, config) {
114
115
  url: { type: String, unique: true, sparse: true, trim: true }
115
116
  },
116
117
 
117
- eshares: {
118
- companyid: { type: String, unique: true, sparse: true, trim: true }
119
- },
120
-
121
118
  related: [{ type: Schema.ObjectId, ref: 'Organization' }],
122
119
 
123
120
  // customer specific (customer id is stored on note)
@@ -172,219 +169,238 @@ module.exports = function(mongoose, config) {
172
169
  // Funds belonging to this organization (which is typically, but not necessarily, what we'd call an investor)
173
170
  // An example of a non-"investor" with funds would be Slack with it's Slack Fund
174
171
  // Another way to put it, these are funds from which this organization makes investments
175
- funds: [{ type: Schema.ObjectId, ref: 'Fund' }],
172
+ funds: [{ type: Schema.ObjectId, ref: 'Fund' }]
176
173
 
177
- // Private
178
- capTable: [{
179
-
180
- customer: { type: Schema.ObjectId, ref: 'Organization', required: true },
181
- shares: { type: Number, default: 0 },
182
- fullyDiluted: { type: Number, default: 0 },
183
- asOfDate: { type: Date },
174
+ });
184
175
 
185
- // todo - ???
186
- optionsAndWarrants: {
187
- unissued: { type: Number },
188
- issuedToUnspecified: { type: Number }
189
- }
190
176
 
191
- }],
192
177
 
193
- // Equity or debt this organization has taken from other organizations
194
- rounds: [{
178
+ ///////////////////////////////////////////////////////////////////////////////////////
179
+ // HELPERS
180
+ ///////////////////////////////////////////////////////////////////////////////////////
195
181
 
196
- roundName: { type: String, trim: true, default: null },
182
+ var getSources = function getSources(people) {
197
183
 
198
- // used when cap table round names are changed and need to be matched to investment data
199
- oldRoundName: { type: String, trim: true, default: null },
184
+ var result = _.compact(_.pluck(people, 'person'));
185
+ if (result.length == 0) return [];
200
186
 
201
- // these three values are public and will be used in the absence of overriding private data
202
- preMoneyValuation: { type: Number, default: 0 },
203
- amountRaised: { type: Number, default: 0 },
204
- closingDate: { type: Date, default: null },
187
+ result = _.compact(_.pluck(result, 'sources'));
188
+ if (result.length == 0) return [];
205
189
 
206
- // Funds that participated in this round
207
- // Recall that a fund belongs to an organization
208
- // This is a public list of funds/orgs that invested
209
- funds: [{
190
+ result = _.flatten(result);
191
+ result = _.compact(result);
210
192
 
211
- fund: { type: Schema.ObjectId, ref: 'Fund', required: true },
193
+ if (result.length == 0) return [];
194
+ if (mongoose.Types.ObjectId.isValid(result[0].person)) return []; // not populated
212
195
 
213
- // the lead on this round from the fund/org
214
- partner: { type: Schema.ObjectId, ref: 'Person', required: false },
196
+ result = _.uniq(result, function(item) { return item.person._id.toString(); });
197
+ result = _.sortBy(result, 'interactions.count');
198
+ result.reverse();
199
+ result = _.sortBy(result, 'name.full');
215
200
 
216
- // not strictly necessary since we have fund
217
- // include because it makes filtering investments data, which is private, much easier
218
- customer: { type: Schema.ObjectId, ref: 'Organization', required: false },
201
+ return result;
219
202
 
220
- // Investments in this round from this fund
221
- // this is all private data
222
- investments: [{
203
+ };
223
204
 
224
- amount: { type: Number, default: 0 },
225
- date: { type: Date, default: null },
226
- lead: { type: Boolean, default: false },
227
- participatingPreferred: { type: Boolean, default: false },
228
- pricePerShare: { type: Number, default: 0 },
229
- fairMarketValue: { type: Number, default: 0 },
230
- notes: { type: String, trim: true, default: null },
231
205
 
232
- // note these fields are duplicative at the round level
233
- // this is private data and will override the round level numbers where appropriate
234
- preMoneyValuation: { type: Number, default: 0 },
235
- amountRaised: { type: Number, default: 0 },
236
- closingDate: { type: Date, default: null },
237
206
 
238
- convertibleNotes: [{
239
- cap: { type: Number, default: 0 },
240
- discount: { type: Number, default: 0 },
241
- interestRate: { type: Number, default: 0 },
242
- maturityDate: { type: Date, default: null },
243
- fairMarketValue: { type: Number, default: 0 },
244
- notes: { type: String, trim: true, default: null }
245
- }]
207
+ ///////////////////////////////////////////////////////////////////////////////////////
208
+ // VIRTUALS
209
+ // Properties that are not persisted to the database
210
+ ///////////////////////////////////////////////////////////////////////////////////////
246
211
 
247
- }]
212
+ Organization.virtual('address.primary').get(function () {
213
+ var primary = _.find(this.contact.address, function(address) {
214
+ return address.primary;
215
+ });
216
+ return primary ? primary.address : null;
217
+ });
248
218
 
249
- }],
219
+ Organization.virtual('board.all').get(function () {
250
220
 
251
- // People that participated in this round
252
- // This is a public list of people that invested
253
- people: [{
254
- person: {type: Schema.ObjectId, ref: 'Person'}
255
- }]
221
+ var self = this;
222
+ var members = [];
256
223
 
257
- }],
224
+ _.each(self.people, function(p) {
225
+ var boardRole = p.board;
226
+ if (boardRole && boardRole != 'none') members.push(p.person);
227
+ });
258
228
 
229
+ members = _.sortBy(members, function(member) {
230
+ if (!member || !member.name) return null;
231
+ return member.name.last + member.name.first;
259
232
  });
260
233
 
261
- ///////////////////////////////
234
+ return members;
262
235
 
263
- Organization.virtual('status').get(function () {
236
+ });
237
+
238
+ Organization.virtual('board.current').get(function () {
264
239
 
265
240
  var self = this;
241
+ var members = [];
266
242
 
267
- // infer from data
243
+ _.each(self.people, function(p) {
244
+ var boardRole = p.board;
245
+ if (p.current && boardRole && boardRole != 'none') members.push(p.person);
246
+ });
268
247
 
269
- if (self.closedOn) return 'Dead';
270
- else if (self.ipo.year) return 'IPO';
271
- else if (self.acquisition.year) return 'Acquired';
272
- else return 'Active';
248
+ members = _.sortBy(members, function(member) {
249
+ if (!member || !member.name) return null;
250
+ return member.name.last + member.name.first;
251
+ });
252
+
253
+ return members;
273
254
 
274
255
  });
275
256
 
276
- Organization.virtual('address.primary').get(function () {
277
- var primary = _.find(this.contact.address, function(address) {
278
- return address.primary;
257
+ Organization.virtual('board.past').get(function () {
258
+
259
+ var self = this;
260
+ var members = [];
261
+
262
+ _.each(self.people, function(p) {
263
+ var boardRole = p.board;
264
+ if (!p.current && boardRole && boardRole != 'none') members.push(p.person);
279
265
  });
280
- return primary ? primary.address : null;
266
+
267
+ members = _.sortBy(members, function(member) {
268
+ if (!member || !member.name) return null;
269
+ return member.name.last + member.name.first;
270
+ });
271
+
272
+ return members;
273
+
281
274
  });
282
275
 
283
- Organization.virtual('sources').get(function () {
276
+ Organization.virtual('employees.all').get(function () {
277
+
278
+ var self = this;
279
+ var result = [];
280
+
281
+ _.each(self.people, function(p) {
282
+ if (!p.leader && (!p.board || p.board == 'none')) result.push(p);
283
+ });
284
+
285
+ result = _.sortBy(result, function(item) {
286
+ if (!item.person || !item.person.name) return null;
287
+ return item.person.name.last + item.person.name.first;
288
+ });
289
+
290
+ return result;
291
+
292
+ });
284
293
 
285
- // Aggregate sources from all people at org
286
- // Sort by interactions descending, full name ascending
294
+ Organization.virtual('employees.current').get(function () {
287
295
 
288
296
  var self = this;
297
+ var result = [];
289
298
 
290
- var result = _.compact(_.pluck(self.people, 'person'));
291
- if (result.length == 0) return [];
299
+ _.each(self.people, function(p) {
300
+ if (p.current && !p.leader && (!p.board || p.board == 'none')) result.push(p);
301
+ });
292
302
 
293
- result = _.compact(_.pluck(result, 'sources'));
294
- if (result.length == 0) return [];
303
+ result = _.sortBy(result, function(item) {
304
+ if (!item.person || !item.person.name) return null;
305
+ return item.person.name.last + item.person.name.first;
306
+ });
295
307
 
296
- result = _.flatten(result);
297
- result = _.compact(result);
308
+ return result;
298
309
 
299
- if (result.length == 0) return [];
300
- if (mongoose.Types.ObjectId.isValid(result[0].person)) return []; // not populated
310
+ });
301
311
 
302
- result = _.uniq(result, function(item) { return item.person._id.toString(); });
303
- result = _.sortBy(result, 'interactions.count');
304
- result.reverse();
305
- result = _.sortBy(result, 'name.full');
312
+ Organization.virtual('employees.past').get(function () {
313
+
314
+ var self = this;
315
+ var result = [];
316
+
317
+ _.each(self.people, function(p) {
318
+ if (!p.current && !p.leader && (!p.board || p.board == 'none')) result.push(p);
319
+ });
320
+
321
+ result = _.sortBy(result, function(item) {
322
+ if (!item.person || !item.person.name) return null;
323
+ return item.person.name.last + item.person.name.first;
324
+ });
306
325
 
307
326
  return result;
308
327
 
309
328
  });
310
329
 
311
- ///////////////////////////////
330
+ Organization.virtual('leaders.all').get(function () {
312
331
 
313
- Organization.methods.merge = function(id) {
314
- if (this.merged.indexOf(id) == -1) {
315
- this.merged.push(id);
316
- return true;
317
- }
318
- else {
319
- return false;
320
- }
321
- };
332
+ var self = this;
333
+ var result = [];
322
334
 
323
- Organization.methods.deleteOrg = function() {
324
- // Soft delete
325
- this.deleted = true;
326
- };
335
+ _.each(self.people, function(p) {
336
+ if (p.leader) result.push(p);
337
+ });
327
338
 
328
- Organization.methods.relateOrg = function(id) {
339
+ result = _.sortBy(result, function(item) {
340
+ if (!item.person || !item.person.name) return null;
341
+ return item.person.name.last + item.person.name.first;
342
+ });
343
+
344
+ return result;
345
+
346
+ });
347
+
348
+ Organization.virtual('leaders.current').get(function () {
329
349
 
330
350
  var self = this;
351
+ var result = [];
331
352
 
332
- var match = _.find(self.related, function(org) {
333
- var orgId = utils.isValidObjectId(org) ? org : org.id;
334
- return orgId.toString() == id.toString();
353
+ _.each(self.people, function(p) {
354
+ if (p.leader && p.current) result.push(p);
335
355
  });
336
356
 
337
- if (!match) { self.related.push(id); }
357
+ result = _.sortBy(result, function(item) {
358
+ if (!item.person || !item.person.name) return null;
359
+ return item.person.name.last + item.person.name.first;
360
+ });
338
361
 
339
- };
362
+ return result;
340
363
 
341
- // todo - what are "basic" fields and how are they used?
342
- Organization.methods.newBasic = function(field, value) {
364
+ });
343
365
 
344
- var compoundField = false;
345
- if(field.split(".").length > 1) {
346
- compoundField = true;
347
- }
366
+ Organization.virtual('leaders.past').get(function () {
348
367
 
349
- if(!compoundField) {
350
- this[field] = value;
351
- } else {
352
- var parentField = field.split('.')[0];
353
- var childField = field.split('.')[1];
368
+ var self = this;
369
+ var result = [];
354
370
 
355
- this[parentField][childField] = value;
356
- }
371
+ _.each(self.people, function(p) {
372
+ if (p.leader && !p.current) result.push(p);
373
+ });
357
374
 
358
- };
375
+ result = _.sortBy(result, function(item) {
376
+ if (!item.person || !item.person.name) return null;
377
+ return item.person.name.last + item.person.name.first;
378
+ });
379
+
380
+ return result;
359
381
 
360
- Organization.methods.updateBasic = function(field, value) {
382
+ });
361
383
 
362
- var compoundField = false;
363
- if(field.split(".").length > 1) {
364
- compoundField = true;
365
- }
384
+ Organization.virtual('status').get(function () {
366
385
 
367
- if(!compoundField) {
368
- var currentVal = this[field];
369
- this[field] = value;
370
- } else {
371
- var parentField = field.split('.')[0];
372
- var childField = field.split('.')[1];
386
+ var self = this;
373
387
 
374
- var currentVal = this[parentField][childField];
375
- this[parentField][childField] = value;
376
- }
388
+ // infer from data
377
389
 
378
- };
390
+ if (self.closedOn) return 'Dead';
391
+ else if (self.ipo && self.ipo.year) return 'IPO';
392
+ else if (self.acquisition && self.acquisition.year) return 'Acquired';
393
+ else return 'Active';
379
394
 
380
- Organization.methods.updateContact = function(field, value) {
395
+ });
381
396
 
382
- var parentField = field.split('.')[0];
383
- var childField = field.split('.')[1];
384
397
 
385
- this[parentField][childField] = value;
386
398
 
387
- };
399
+ ///////////////////////////////////////////////////////////////////////////////////////
400
+ // METHODS
401
+ // Methods operate on a single document that has already been returned
402
+ // Note that running a method on a document does not save it
403
+ ///////////////////////////////////////////////////////////////////////////////////////
388
404
 
389
405
  Organization.methods.addPerson = function(personToAdd) {
390
406
 
@@ -406,151 +422,375 @@ module.exports = function(mongoose, config) {
406
422
 
407
423
  };
408
424
 
409
- Organization.methods.removePerson = function(personIdToRemove) {
410
-
411
- this.people = _.reject(this.people, function(existingPerson) {
412
- var id = utils.isValidObjectId(existingPerson.person) ? existingPerson.person : existingPerson.person._id;
413
- return id.toString() == personIdToRemove.toString();
414
- });
425
+ Organization.methods.deleteOrg = function() {
426
+ // Soft delete
427
+ this.deleted = true;
428
+ };
415
429
 
430
+ Organization.methods.merge = function(id) {
431
+ if (this.merged.indexOf(id) == -1) {
432
+ this.merged.push(id);
433
+ return true;
434
+ }
435
+ else {
436
+ return false;
437
+ }
416
438
  };
417
439
 
418
- Organization.methods.updateFunds = function(funds) {
440
+ Organization.methods.relateOrg = function(id) {
419
441
 
420
442
  var self = this;
421
443
 
422
- // todo - ??? - funds invested into this org or funds this org invests from?
444
+ var match = _.find(self.related, function(org) {
445
+ var orgId = utils.isValidObjectId(org) ? org : org.id;
446
+ return orgId.toString() == id.toString();
447
+ });
423
448
 
424
- if (!self.portfolioCompany) return;
449
+ if (!match) { self.related.push(id); }
425
450
 
426
- // wipe funds and start fresh
427
- self.portfolioCompany.fundsInvested = funds;
451
+ };
428
452
 
429
- // Checking if it's a valid object id is a shortcut
430
- // TODO: The alternative is to get a list of funds and verify against that
431
- // TODO: also would be good to check for dupes
432
- // This is used internally, in admin, so let's punt for now
453
+ Organization.methods.removePerson = function(personIdToRemove) {
433
454
 
434
- //_.each(fundIds, function(fundId) {
435
- // if (utilities.isValidObjectId(fundId.id)) {
436
- // self.fundsInvested.push(fundId);
437
- // };
438
- //});
455
+ this.people = _.reject(this.people, function(existingPerson) {
456
+ var id = utils.isValidObjectId(existingPerson.person) ? existingPerson.person : existingPerson.person._id;
457
+ return id.toString() == personIdToRemove.toString();
458
+ });
439
459
 
440
460
  };
441
461
 
442
- Organization.methods.updateChair = function(chairObj) {
443
-
444
- var self = this;
445
462
 
446
- if (!self.portfolioCompany) return;
447
463
 
448
- // todo - needs customer id
464
+ //////////////////////////////////////////////////////
465
+ // STATICS
466
+ // Statics query the entire collection
467
+ //////////////////////////////////////////////////////
449
468
 
450
- switch(chairObj.num) {
451
- case 'chair1':
452
- if(chairObj.val && chairObj.val != "") {
453
- self.portfolioCompany.chairs.first = chairObj.val;
454
- } else {
455
- self.portfolioCompany.chairs.first = {};
456
- }
457
- break;
469
+ Organization.statics.addNote = function(organizationId, creatorPersonId, text, cb) {
458
470
 
459
- case 'chair2':
460
- if(chairObj.val && chairObj.val != "") {
461
- self.portfolioCompany.chairs.second = chairObj.val;
462
- } else {
463
- self.portfolioCompany.chairs.second = {}
464
- }
465
- break;
466
- }
471
+ Note.createForModel({
472
+ createdBy: creatorPersonId,
473
+ text: text
474
+ }, this, organizationId, cb);
467
475
 
468
476
  };
469
477
 
470
- // todo - change this to exit (with acquisition or ipo)
471
- Organization.methods.updateAcquisition = function(aObj) {
478
+ Organization.statics.deleteNote = function(noteId, cb) {
479
+
480
+ // Delete the note itself along with any references
472
481
 
473
482
  var self = this;
474
483
 
475
- if (!self.portfolioCompany) return;
484
+ var removeReferences = function removeReferences(callback) {
485
+ self.update({}, {
486
+ $pull: { 'notes' : [noteId] }
487
+ }, callback);
488
+ };
476
489
 
477
- self.portfolioCompany.acquisition = aObj;
490
+ async.series([
491
+ Note.delete.bind(Note, noteId),
492
+ removeReferences
493
+ ], function(err, results) {
494
+ return cb(err, null);
495
+ });
478
496
 
479
497
  };
480
498
 
481
- Organization.methods.removeInvestmentsAndDebtsByCustomer = function(investor) {
499
+ Organization.statics.findByDomains = function findByDomains(domains, cb) {
500
+ var query = this.find({ $or: [ {'website': { $in : domains }}, {'websiteAliases': { $in : domains }} ], 'deleted': {$ne: true} });
501
+ query.exec(cb);
502
+ };
482
503
 
504
+ Organization.statics.findByFunds = function findByFunds(fundids, cb) {
483
505
  var self = this;
506
+ self
507
+ .find({ 'funds': { $in : fundids }, 'deleted': {$ne: true} })
508
+ .select('name logoUrl')
509
+ .exec(cb);
510
+ };
484
511
 
485
- // todo - change to round.funds and capTable.customer
486
-
487
- if (!self.portfolioCompany) return;
488
-
489
- if(self.portfolioCompany.investments.length > 0) {
490
- self.portfolioCompany.investments = _.reject(self.portfolioCompany.investments, function(i) {
491
- return String(i.source) == String(investor._id);
492
- });
493
- }
512
+ Organization.statics.findByIds = function findByIds(ids, cb) {
494
513
 
495
- if(self.portfolioCompany.debts.length > 0) {
496
- self.portfolioCompany.debts = _.reject(self.portfolioCompany.debts, function(d) {
497
- return String(d.source) == String(investor._id);
498
- });
499
- }
514
+ var self = this;
500
515
 
501
- if(self.portfolioCompany.fundsInvested.length > 0) {
502
- self.portfolioCompany.fundsInvested = _.reject(self.portfolioCompany.fundsInvested, function(f) {
503
- return String(f.investor._id || f.investor) == String(investor._id);
504
- });
505
- }
516
+ self
517
+ .find({ '_id': { $in : ids }})
518
+ .populate('people.person', 'name avatarUrl title calendarEventSummaries sources')
519
+ .populate('related', 'name logoUrl')
520
+ .populate('notes')
521
+ .populate('chairs.first', 'name avatarUrl title')
522
+ .populate('chairs.second', 'name avatarUrl title')
523
+ .populate('funds', 'name hexColorCode')
524
+ .deepPopulate([
525
+ 'people.person.sources.person',
526
+ 'people.person.calendarEventSummaries.attendees.internal',
527
+ 'people.person.calendarEventSummaries.attendees.external',
528
+ 'people.person.calendarEventSummaries.notes.createdBy',
529
+ 'notes.createdBy'
530
+ ], {
531
+ populate: {
532
+ 'people.person.sources.person': { select: 'name avatarUrl title' },
533
+ 'people.person.calendarEventSummaries.attendees.internal': { select: 'name avatarUrl title' },
534
+ 'people.person.calendarEventSummaries.attendees.external': { select: 'name avatarUrl title' },
535
+ 'people.person.calendarEventSummaries.notes.createdBy': { select: 'name avatarUrl title' },
536
+ 'notes.createdBy': { select: 'name avatarUrl title' }
537
+ }
538
+ })
539
+ .exec(cb);
506
540
 
507
541
  };
508
542
 
509
- Organization.methods.addCompanyToInvestor = function(idOfCompanyToAdd) {
543
+ Organization.statics.findByPerson = function findByPerson(personId, cb) {
544
+
545
+ // Given a person id, get all organizations with that person
510
546
 
511
547
  var self = this;
512
- if (!self.investor) return;
513
548
 
514
- var match = _.find(self.investor.companies, function(company) {
515
- var companyId = utils.isValidObjectId(company) ? company : company.id;
516
- return companyId.toString() == idOfCompanyToAdd.toString();
517
- });
549
+ self
550
+ .find({ $or:
551
+ [
552
+ { 'people.person': personId },
553
+ { 'chairs.first': personId },
554
+ { 'chairs.second': personId }
555
+ ],
556
+ 'deleted': { $ne: true }
557
+ })
558
+ .exec(cb);
518
559
 
519
- if (!match) self.investor.companies.push(idOfCompanyToAdd);
560
+ };
520
561
 
562
+ Organization.statics.findBySlug = function findBySlug(slug, cb) {
563
+ this.findOne({ slug: slug, 'deleted': {$ne: true} }).exec(cb);
521
564
  };
522
565
 
523
- Organization.methods.addFundToInvestor = function(idOfFundToAdd) {
566
+ Organization.statics.findBySlugs = function findBySlugs(slugs, cb) {
567
+ this.find({ 'slug': { $in : slugs }, 'deleted': {$ne: true} }).exec(cb);
568
+ };
524
569
 
525
- var self = this;
526
- if (!self.investor) return;
570
+ Organization.statics.findDupes = function findDupes(org, options, cb) {
527
571
 
528
- var match = _.find(self.investor.funds, function(fund) {
529
- var fundId = utils.isValidObjectId(fund) ? fund : fund.id;
530
- return fundId.toString() == idOfFundToAdd.toString();
531
- });
572
+ if (!cb) throw new Error('cb is required');
573
+ if (!org || (!org.name && !org.website)) return cb(null, []);
532
574
 
533
- if (!match) self.investor.funds.push(idOfFundToAdd);
575
+ this.search({
576
+ name: org.name,
577
+ domain: utils.getDomain(org.website)
578
+ }, options, function(err, orgs) {
534
579
 
535
- };
580
+ if (err) return cb(err, null);
581
+ if (!orgs) return cb(null, null);
536
582
 
537
- ///////////////////////
538
-
539
- // search for orgs based on fields provided
540
- // data format, each field is optional
541
- // {
542
- // domain: ''
543
- // name: ''
544
- // }
545
- // options:
546
- // fuzzy: searches within strings, e.g., searching for "venture" will match "ff Venture Capital"
547
- // non-fuzzy does a full-string match, e.g., "ffvc" will match "ffvc" but "ff" will not
548
- // both fuzzy and non are case-insensitive
549
- // queryFirstWord: tokenizes first word and searches for it
550
- // removeRelatedFromTopLevel: related people are removed from top-level results
551
- Organization.statics.search = function search(data, options, cb) {
583
+ // exclude passed in org from results
584
+ orgs = _.reject(orgs, function(o) {
585
+ return o.id == org.id;
586
+ });
552
587
 
553
- // validate input
588
+ return cb(null, orgs);
589
+
590
+ });
591
+
592
+ };
593
+
594
+ Organization.statics.flag = function(orgId, resolved, text, username, cb) {
595
+
596
+ this.findById(orgId).exec(function(err, org) {
597
+
598
+ org.flagged.resolved = resolved;
599
+ org.flagged.text = text;
600
+ org.flagged.by = username;
601
+ org.flagged.on = Date.now();
602
+
603
+ org.save(cb);
604
+
605
+ });
606
+
607
+ };
608
+
609
+ Organization.statics.getBoardsOfPerson = function getBoardsOfPerson(personId, cb) {
610
+
611
+ var self = this;
612
+
613
+ self
614
+ .find({
615
+ 'deleted': { $ne: true },
616
+ 'people': {
617
+ $elemMatch: {
618
+ 'person': personId,
619
+ 'board': { $in: ['chairman', 'director', 'observer'] }
620
+ }
621
+ }
622
+ })
623
+ .select('name avatarUrl')
624
+ .exec(cb);
625
+
626
+ };
627
+
628
+ Organization.statics.getByFund = function getByFund(id, cb) {
629
+ var self = this;
630
+ self
631
+ .findOne({ 'funds': id, 'deleted': {$ne: true} })
632
+ .select('name logoUrl')
633
+ .exec(cb);
634
+ };
635
+
636
+ Organization.statics.getById = function getById(id, cb) {
637
+
638
+ var self = this;
639
+
640
+ self
641
+ .findById(id)
642
+ .populate('people.person', 'name avatarUrl title calendarEventSummaries sources')
643
+ .populate('related', 'name logoUrl')
644
+ .populate('notes')
645
+ .populate('chairs.first', 'name avatarUrl title')
646
+ .populate('chairs.second', 'name avatarUrl title')
647
+ .populate('funds', 'name hexColorCode closeDate')
648
+ .deepPopulate([
649
+ 'people.person.sources.person',
650
+ 'people.person.calendarEventSummaries.attendees.internal',
651
+ 'people.person.calendarEventSummaries.attendees.external',
652
+ 'people.person.calendarEventSummaries.notes.createdBy',
653
+ 'notes.createdBy'
654
+ ], {
655
+ populate: {
656
+ 'people.person.sources.person': { select: 'name avatarUrl title' },
657
+ 'people.person.calendarEventSummaries.attendees.internal': { select: 'name avatarUrl title' },
658
+ 'people.person.calendarEventSummaries.attendees.external': { select: 'name avatarUrl title' },
659
+ 'people.person.calendarEventSummaries.notes.createdBy': { select: 'name avatarUrl title' },
660
+ 'notes.createdBy': { select: 'name avatarUrl title' }
661
+ }
662
+ })
663
+ .exec(cb);
664
+
665
+ };
666
+
667
+ Organization.statics.getChairsOfPerson = function getChairsOfPerson(personId, cb) {
668
+
669
+ var self = this;
670
+
671
+ self
672
+ .find({ $or:
673
+ [
674
+ { 'chairs.first': personId },
675
+ { 'chairs.second': personId }
676
+ ],
677
+ 'deleted': { $ne: true }
678
+ })
679
+ .exec(cb);
680
+
681
+ };
682
+
683
+ Organization.statics.getSources = function(id, cb) {
684
+
685
+ var self = this;
686
+
687
+ self
688
+ .findById(id)
689
+ .populate('people.person', 'name avatarUrl title calendarEventSummaries sources')
690
+ .deepPopulate([
691
+ 'people.person.sources.person'
692
+ ], {
693
+ populate: {
694
+ 'people.person.sources.person': { select: 'name avatarUrl title' }
695
+ }
696
+ })
697
+ .exec(function(err, org) {
698
+
699
+ if (err) return cb(err, null);
700
+ if (!org) return cb(null, null);
701
+
702
+ self.findById(config.CUSTOMER_ID, function(err, customer) {
703
+
704
+ if (err) return cb(err, null);
705
+ if (!customer) return cb(null, null);
706
+
707
+ var current = [];
708
+ var past = [];
709
+ var all = [];
710
+ var orgSources = getSources(org.people);
711
+
712
+ _.each(customer.people, function(p) {
713
+
714
+ var source = _.find(orgSources, function(os) {
715
+ return os.person._id.toString() == p.person.toString();
716
+ });
717
+
718
+ if (!source) return;
719
+
720
+ if (p.current) current.push(source);
721
+ if (!p.current) past.push(source);
722
+ all.push(source);
723
+
724
+ });
725
+
726
+ return cb(null, {
727
+ all: all,
728
+ current: current,
729
+ past: past
730
+ });
731
+
732
+ });
733
+
734
+ });
735
+
736
+ };
737
+
738
+ Organization.statics.listAllFlagged = function (cb) {
739
+
740
+ var query = this.find({'flagged.resolved': false, 'flagged.text': { $ne: '' }, 'deleted': {$ne: true}});
741
+ query.exec(function (err, orgs) {
742
+
743
+ if (err) return cb(err, null);
744
+
745
+ orgs = _.sortBy(orgs, function(org){
746
+ var timestamp = new Date(org.flagged.on);
747
+ timestamp = timestamp.getTime();
748
+ return timestamp;
749
+ });
750
+ orgs = orgs.reverse();
751
+
752
+ return cb(null, orgs);
753
+
754
+ });
755
+
756
+ };
757
+
758
+ Organization.statics.listPage = function (skip, cb) {
759
+
760
+ this
761
+ .find({'deleted': {$ne: true}})
762
+ .sort('name')
763
+ .skip(skip)
764
+ .limit(100)
765
+ .exec(cb)
766
+
767
+ };
768
+
769
+ Organization.statics.removeById = function(id, cb) {
770
+
771
+ if (!id) { return cb(new Error('id is required'), null); }
772
+ if (!cb) { throw new Error('cb is required'); }
773
+
774
+ this.remove({_id: id}, cb);
775
+
776
+ };
777
+
778
+ Organization.statics.search = function search(data, options, cb) {
779
+
780
+ // search for orgs based on fields provided
781
+ // data format, each field is optional
782
+ // {
783
+ // domain: ''
784
+ // name: ''
785
+ // }
786
+ // options:
787
+ // fuzzy: searches within strings, e.g., searching for "venture" will match "ff Venture Capital"
788
+ // non-fuzzy does a full-string match, e.g., "ffvc" will match "ffvc" but "ff" will not
789
+ // both fuzzy and non are case-insensitive
790
+ // queryFirstWord: tokenizes first word and searches for it
791
+ // removeRelatedFromTopLevel: related people are removed from top-level results
792
+
793
+ // validate input
554
794
  if (!cb) throw new Error('cb is required');
555
795
  if (!data || (!data.name && !data.domain)) return cb(null, []);
556
796
 
@@ -686,162 +926,16 @@ module.exports = function(mongoose, config) {
686
926
 
687
927
  };
688
928
 
689
- Organization.statics.findDupes = function findDupes(org, options, cb) {
690
-
691
- if (!cb) throw new Error('cb is required');
692
- if (!org || (!org.name && !org.website)) return cb(null, []);
693
-
694
- this.search({
695
- name: org.name,
696
- domain: utils.getDomain(org.website)
697
- }, options, function(err, orgs) {
698
-
699
- if (err) return cb(err, null);
700
- if (!orgs) return cb(null, null);
701
-
702
- // exclude passed in org from results
703
- orgs = _.reject(orgs, function(o) {
704
- return o.id == org.id;
705
- });
706
-
707
- return cb(null, orgs);
708
-
709
- });
710
-
929
+ Organization.statics.setCustomerPortfolio = function setCustomerPortfolio(portfolio) {
930
+ var self = this;
931
+ self.customerPortfolio = portfolio;
932
+ return self.customerPortfolio;
711
933
  };
712
934
 
713
935
  Organization.statics.stats = function stats(cb) {
714
936
  this.collection.stats(cb);
715
937
  };
716
938
 
717
- Organization.statics.findByPerson = function findByPerson(personId, cb) {
718
- this.find({ 'people.person': personId, 'deleted': {$ne: true} }).exec(cb);
719
- };
720
-
721
- Organization.statics.findBySlug = function findBySlug(slug, cb) {
722
- this.find({ slug: slug, 'deleted': {$ne: true} }).exec(cb);
723
- };
724
-
725
- Organization.statics.findBySlugs = function findBySlugs(slugs, cb) {
726
- this.find({ 'slug': { $in : slugs }, 'deleted': {$ne: true} }).exec(cb);
727
- };
728
-
729
- Organization.statics.findByDomains = function findByDomains(domains, cb) {
730
- var query = this.find({ $or: [ {'website': { $in : domains }}, {'websiteAliases': { $in : domains }} ], 'deleted': {$ne: true} });
731
- query.exec(cb);
732
- };
733
-
734
- Organization.statics.findByIds = function findByIds(ids, cb) {
735
-
736
- var self = this;
737
-
738
- self
739
- .find({ '_id': { $in : ids }})
740
- .populate('people.person', 'name avatarUrl title calendarEventSummaries sources')
741
- .populate('related', 'name logoUrl')
742
- .populate('notes')
743
- .populate('chairs.first', 'name avatarUrl title')
744
- .populate('chairs.second', 'name avatarUrl title')
745
- .populate('funds', 'name hexColorCode')
746
- .populate('rounds.funds.fund', 'name hexColorCode')
747
- .populate('rounds.funds.partner', 'name avatarUrl title')
748
- .populate('rounds.people.person', 'name avatarUrl title')
749
- .deepPopulate([
750
- 'people.person.sources.person',
751
- 'people.person.calendarEventSummaries.attendees.internal',
752
- 'people.person.calendarEventSummaries.attendees.external',
753
- 'people.person.calendarEventSummaries.notes.createdBy',
754
- 'notes.createdBy'
755
- ], {
756
- populate: {
757
- 'people.person.sources.person': { select: 'name avatarUrl title' },
758
- 'people.person.calendarEventSummaries.attendees.internal': { select: 'name avatarUrl title' },
759
- 'people.person.calendarEventSummaries.attendees.external': { select: 'name avatarUrl title' },
760
- 'people.person.calendarEventSummaries.notes.createdBy': { select: 'name avatarUrl title' },
761
- 'notes.createdBy': { select: 'name avatarUrl title' }
762
- }
763
- })
764
- .exec(cb);
765
-
766
- };
767
-
768
- Organization.statics.getById = function getById(id, cb) {
769
-
770
- var self = this;
771
-
772
- self
773
- .findById(id)
774
- .populate('people.person', 'name avatarUrl title calendarEventSummaries sources')
775
- .populate('related', 'name logoUrl')
776
- .populate('notes')
777
- .populate('chairs.first', 'name avatarUrl title')
778
- .populate('chairs.second', 'name avatarUrl title')
779
- .populate('funds', 'name hexColorCode')
780
- .populate('rounds.funds.fund', 'name hexColorCode')
781
- .populate('rounds.funds.partner', 'name avatarUrl title')
782
- .populate('rounds.people.person', 'name avatarUrl title')
783
- .deepPopulate([
784
- 'people.person.sources.person',
785
- 'people.person.calendarEventSummaries.attendees.internal',
786
- 'people.person.calendarEventSummaries.attendees.external',
787
- 'people.person.calendarEventSummaries.notes.createdBy',
788
- 'notes.createdBy'
789
- ], {
790
- populate: {
791
- 'people.person.sources.person': { select: 'name avatarUrl title' },
792
- 'people.person.calendarEventSummaries.attendees.internal': { select: 'name avatarUrl title' },
793
- 'people.person.calendarEventSummaries.attendees.external': { select: 'name avatarUrl title' },
794
- 'people.person.calendarEventSummaries.notes.createdBy': { select: 'name avatarUrl title' },
795
- 'notes.createdBy': { select: 'name avatarUrl title' }
796
- }
797
- })
798
- .exec(cb);
799
-
800
- };
801
-
802
- Organization.statics.getPortfolio = function getPortfolio(orgId, cb) {
803
-
804
- var self = this;
805
-
806
- self
807
- .find({ 'rounds.funds.customer': orgId })
808
- .select('name logoUrl description exit filters contact.addresses')
809
- .sort('name')
810
- .exec(cb);
811
-
812
- };
813
-
814
- Organization.statics.listPage = function (skip, cb) {
815
-
816
- this
817
- .find({'deleted': {$ne: true}})
818
- .sort('name')
819
- .skip(skip)
820
- .limit(100)
821
- .exec(cb)
822
-
823
- };
824
-
825
- Organization.statics.listAllFlagged = function (cb) {
826
-
827
- var query = this.find({'flagged.resolved': false, 'flagged.text': { $ne: '' }, 'deleted': {$ne: true}});
828
- query.exec(function (err, orgs) {
829
-
830
- if (err) return cb(err, null);
831
-
832
- orgs = _.sortBy(orgs, function(org){
833
- var timestamp = new Date(org.flagged.on);
834
- timestamp = timestamp.getTime();
835
- return timestamp;
836
- });
837
- orgs = orgs.reverse();
838
-
839
- return cb(null, orgs);
840
-
841
- });
842
-
843
- };
844
-
845
939
  Organization.statics.upsert = function(org, username, cb) {
846
940
 
847
941
  if (!org) { return cb(new Error('Organization is required'), null); }
@@ -854,61 +948,13 @@ module.exports = function(mongoose, config) {
854
948
 
855
949
  };
856
950
 
857
- Organization.statics.removeById = function(id, cb) {
858
-
859
- if (!id) { return cb(new Error('id is required'), null); }
860
- if (!cb) { throw new Error('cb is required'); }
861
-
862
- this.remove({_id: id}, cb);
863
-
864
- };
865
-
866
- Organization.statics.flag = function(orgId, resolved, text, username, cb) {
867
-
868
- this.findById(orgId).exec(function(err, org) {
869
-
870
- org.flagged.resolved = resolved;
871
- org.flagged.text = text;
872
- org.flagged.by = username;
873
- org.flagged.on = Date.now();
874
-
875
- org.save(cb);
876
951
 
877
- });
878
952
 
879
- };
880
-
881
- Organization.statics.addNote = function(organizationId, creatorPersonId, text, cb) {
882
-
883
- Note.createForModel({
884
- createdBy: creatorPersonId,
885
- text: text
886
- }, this, organizationId, cb);
887
-
888
- };
889
-
890
- Organization.statics.deleteNote = function(noteId, cb) {
891
-
892
- // Delete the note itself along with any references
893
-
894
- var self = this;
895
-
896
- var removeReferences = function removeReferences(callback) {
897
- self.update({}, {
898
- $pull: { 'notes' : [noteId] }
899
- }, callback);
900
- };
901
-
902
- async.series([
903
- Note.delete.bind(Note, noteId),
904
- removeReferences
905
- ], function(err, results) {
906
- return cb(err, null);
907
- });
908
-
909
- };
910
-
911
- ///////////////////////////////
953
+ ///////////////////////////////////////////////////////////////////////////////////////
954
+ // HOOKS
955
+ // Pre-save: fired on every document before it is saved
956
+ // Post-init: fired on every document when it's returned from a query
957
+ ///////////////////////////////////////////////////////////////////////////////////////
912
958
 
913
959
  Organization.pre('save', function(next) {
914
960
 
@@ -928,74 +974,44 @@ module.exports = function(mongoose, config) {
928
974
 
929
975
  });
930
976
 
931
- Organization.post('init', function() {
977
+ Organization.post('init', function(doc, next) {
932
978
 
933
- // Protect private data and merge public and private data
979
+ // Protect private data
980
+ // Merge public and private data
934
981
 
935
- var self = this;
936
982
  var CUSTOMER_ID = config.CUSTOMER_ID;
937
983
 
938
- if (CUSTOMER_ID == 'GLOBAL_PROCESS') return;
939
-
940
- var protectCustomerData = function protectCustomerData() {
941
-
942
- // remove customer details if not viewing self
943
- if (self._id.toString() != CUSTOMER_ID) self.customer = {};
984
+ if (CUSTOMER_ID == 'GLOBAL_PROCESS') return next();
944
985
 
945
- self.filters = _.reject(self.filters, function(item) { return item.customer != CUSTOMER_ID; });
946
- self.docs = _.reject(self.docs, function(item) { return item.customer != CUSTOMER_ID; });
947
- self.iLevel = _.reject(self.iLevel, function(item) { return item.customer != CUSTOMER_ID; });
948
- self.chairs = _.reject(self.chairs, function(item) { return item.customer != CUSTOMER_ID; });
949
- self.capTable = _.reject(self.capTable, function(item) { return item.customer != CUSTOMER_ID; });
950
-
951
- // investment data
952
- _.each(self.rounds, function(round) {
953
- _.each(round.funds, function(fund) {
954
- if (fund.customer != CUSTOMER_ID) fund.investments = [];
955
- });
956
- });
957
-
958
- };
986
+ // remove customer details if not viewing self
987
+ if (doc._id.toString() != CUSTOMER_ID) doc.customer = {};
959
988
 
960
- var mergePublicAndPrivateData = function mergePublicAndPrivateData() {
989
+ doc.filters = _.reject(doc.filters, function(item) { return item.customer != CUSTOMER_ID; });
990
+ doc.docs = _.reject(doc.docs, function(item) { return item.customer != CUSTOMER_ID; });
991
+ doc.iLevel = _.reject(doc.iLevel, function(item) { return item.customer != CUSTOMER_ID; });
992
+ doc.chairs = _.reject(doc.chairs, function(item) { return item.customer != CUSTOMER_ID; });
961
993
 
962
- // rounds are public
963
- _.each(self.rounds, function(round) {
994
+ // people
995
+ _.each(doc.people, function(person) {
964
996
 
965
- // note that public versions of the following data exist at this level:
966
- // preMoneyValuation
967
- // amountRaised
968
- // closingDate
969
- // this data will be overwritten if private data belonging to the current customer is present
997
+ // always include if explicitly public
998
+ if (person.public) return;
970
999
 
971
- // participating funds are public
972
- _.each(round.funds, function(fund) {
1000
+ // if not public, keep it in if org is in the current customer's portfolio. otherwise remove it.
1001
+ // todo
1002
+ console.log(customerPortfolio);
973
1003
 
974
- if (fund.customer != CUSTOMER_ID) return;
975
- if (!fund.investments || fund.investments.length == 0) return;
976
-
977
- // there can be multiple investments in a single round from the same fund
978
- // use the most recent
979
- var mostRecentInvestment = _.max(fund.investments, function(investment) { return investment.date; });
980
-
981
- // this fund belongs to the current customer
982
- // if we have private data, use it rather than the public data
983
- if (mostRecentInvestment) {
984
- if (mostRecentInvestment.preMoneyValuation) round.preMoneyValuation = mostRecentInvestment.preMoneyValuation;
985
- if (mostRecentInvestment.amountRaised) round.amountRaised = mostRecentInvestment.amountRaised;
986
- if (mostRecentInvestment.closingDate) round.closingDate = mostRecentInvestment.closingDate;
987
- }
1004
+ });
988
1005
 
989
- });
1006
+ return next();
990
1007
 
991
- });
1008
+ });
992
1009
 
993
- };
994
1010
 
995
- protectCustomerData();
996
- mergePublicAndPrivateData();
997
1011
 
998
- });
1012
+ ///////////////////////////////////////////////////////////////////////////////////////
1013
+ // CONFIG
1014
+ ///////////////////////////////////////////////////////////////////////////////////////
999
1015
 
1000
1016
  Organization.set('toJSON', { virtuals: true });
1001
1017
  Organization.set('autoIndex', true);
@@ -1006,6 +1022,6 @@ module.exports = function(mongoose, config) {
1006
1022
  var deepPopulate = require('mongoose-deep-populate')(mongoose);
1007
1023
  Organization.plugin(deepPopulate);
1008
1024
 
1009
- mongoose.model('Organization', Organization, 'Organization_new');
1025
+ mongoose.model('Organization', Organization, 'organization_new');
1010
1026
 
1011
1027
  };