@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/Person.js
ADDED
|
@@ -0,0 +1,1163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = function(mongoose, config) {
|
|
4
|
+
|
|
5
|
+
var customerCompanyIds = [];
|
|
6
|
+
|
|
7
|
+
var
|
|
8
|
+
|
|
9
|
+
Schema = mongoose.Schema,
|
|
10
|
+
LimitedPartner = mongoose.model('LimitedPartner'),
|
|
11
|
+
Note = mongoose.model('Note'),
|
|
12
|
+
Crypto = require('@dhyasama/ffvc-crypto'),
|
|
13
|
+
crypto = new Crypto({'key': config.crypto.key}),
|
|
14
|
+
domain = config.domain,
|
|
15
|
+
utils = require('@dhyasama/ffvc-utilities'),
|
|
16
|
+
async = require('async'),
|
|
17
|
+
env = process.env.NODE_ENV || 'development',
|
|
18
|
+
bcrypt = require('bcrypt'),
|
|
19
|
+
_ = require('underscore'),
|
|
20
|
+
phoneFormatter = require('phone-formatter'),
|
|
21
|
+
PhoneNumber = require( 'awesome-phonenumber'),
|
|
22
|
+
validator = require('validator'),
|
|
23
|
+
escapeStringRegexp = require('escape-string-regexp');
|
|
24
|
+
|
|
25
|
+
var Person = new Schema({
|
|
26
|
+
|
|
27
|
+
name: {
|
|
28
|
+
first: { type: String, default: '', index: true },
|
|
29
|
+
last: { type: String, default: '', index: true }
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
slug: { type: String, required: true, unique: true, sparse: false, trim: true, lowercase: true },
|
|
33
|
+
|
|
34
|
+
contact: {
|
|
35
|
+
|
|
36
|
+
phone: [{
|
|
37
|
+
type: { type: String, enum: ['home', 'work', 'assistant', 'mobile', 'other'] },
|
|
38
|
+
number: { type: String, default: '' },
|
|
39
|
+
primary: { type: Boolean, default: false }
|
|
40
|
+
}],
|
|
41
|
+
|
|
42
|
+
email: [{
|
|
43
|
+
type: { type: String, enum: ['personal', 'work', 'assistant', 'other'] },
|
|
44
|
+
email: { type: String, default: '', es_type: 'text' },
|
|
45
|
+
primary: { type: Boolean, default: false }
|
|
46
|
+
}],
|
|
47
|
+
|
|
48
|
+
address: [{
|
|
49
|
+
type: { type: String, default: '' },
|
|
50
|
+
street: { type: String, default: '' },
|
|
51
|
+
suite: { type: String, default: '' },
|
|
52
|
+
city: { type: String, default: '' },
|
|
53
|
+
state: { type: String, default: '' },
|
|
54
|
+
country: { type: String, default: '' },
|
|
55
|
+
postalCode: { type: String, default: '' },
|
|
56
|
+
primary: { type: Boolean, default: false }
|
|
57
|
+
}]
|
|
58
|
+
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
title: { type: String, default: null, trim: true },
|
|
62
|
+
|
|
63
|
+
birthday: { type: String, default: null, trim: true },
|
|
64
|
+
|
|
65
|
+
bio: { type: String, default: null, trim: true },
|
|
66
|
+
|
|
67
|
+
website: { type: String, default: null, required: false, trim: true, lowercase: true },
|
|
68
|
+
|
|
69
|
+
crunchbase: {
|
|
70
|
+
uuid: { type: String, unique: true, required: false, sparse: true, trim: true }
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
vCardUrl: { type: String, default: null, trim: true },
|
|
74
|
+
|
|
75
|
+
social: {
|
|
76
|
+
twitter: { type: String, trim: true, unique: true, sparse: true },
|
|
77
|
+
facebook: { type: String, trim: true, unique: true, sparse: true },
|
|
78
|
+
linkedin: { type: String, trim: true, unique: true, sparse: true }
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
avatarUrl: { type: String, default: null, trim: true },
|
|
82
|
+
|
|
83
|
+
sources: [{
|
|
84
|
+
_id: false,
|
|
85
|
+
person: { type: Schema.ObjectId, ref: 'Person' },
|
|
86
|
+
customer: { type: Schema.ObjectId, ref: 'Organization' },
|
|
87
|
+
interactions: {
|
|
88
|
+
calendar: { type: Number, required: false, default: 0 },
|
|
89
|
+
email: { type: Number, required: false, default: 0 }
|
|
90
|
+
}
|
|
91
|
+
}],
|
|
92
|
+
|
|
93
|
+
notes: [ { type: Schema.ObjectId, ref: 'Note' } ],
|
|
94
|
+
|
|
95
|
+
// this only relates to access for lps
|
|
96
|
+
syndication: {
|
|
97
|
+
notify: { type: Boolean, default: false }
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// this only relates to access for lps
|
|
101
|
+
subscriptions: [{ id: { type: Schema.ObjectId, required: false, default: null }}],
|
|
102
|
+
|
|
103
|
+
account: { type: Schema.ObjectId, ref: 'Account', required: false },
|
|
104
|
+
|
|
105
|
+
entered: {
|
|
106
|
+
by: { type: String, default: null, trim: true },
|
|
107
|
+
on: { type: Date, default: Date.now }
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
flagged: {
|
|
111
|
+
by: { type: String, default: null, trim: true },
|
|
112
|
+
on: { type: Date, default: Date.now },
|
|
113
|
+
text: { type: String, default: null, trim: true },
|
|
114
|
+
resolved: { type: Boolean, default: false }
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
merged: [{ type: Schema.ObjectId, ref: 'Person' }],
|
|
118
|
+
|
|
119
|
+
deleted: { type: Boolean, default: false },
|
|
120
|
+
|
|
121
|
+
aliases: [ { type: String, trim: true } ],
|
|
122
|
+
|
|
123
|
+
related: [{ type: Schema.ObjectId, ref: 'Person' }],
|
|
124
|
+
|
|
125
|
+
calendarEventSummaries: [
|
|
126
|
+
{
|
|
127
|
+
calendarEventId: { type: Schema.ObjectId, ref: 'CalendarEvent', required: true },
|
|
128
|
+
providerEventId: { type: String, required: true, index: true, trim: true },
|
|
129
|
+
totemCustomerId: { type: Schema.ObjectId, ref: 'Organization', required: true },
|
|
130
|
+
summary: { type: String, required: true, trim: true },
|
|
131
|
+
startTime: { type: Date },
|
|
132
|
+
attendees: {
|
|
133
|
+
internal: [{ type: Schema.ObjectId, ref: 'Person' }],
|
|
134
|
+
external: [{ type: Schema.ObjectId, ref: 'Person' }]
|
|
135
|
+
},
|
|
136
|
+
notes: [ { type: Schema.ObjectId, ref: 'Note' } ]
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
/////////////
|
|
143
|
+
|
|
144
|
+
Person.virtual('name.full').get(function () {
|
|
145
|
+
return this.name.first + ' ' + this.name.last;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
Person.virtual('avatar').get(function() {
|
|
149
|
+
return this.avatarUrl || domain + '/media/moustache.jpg';
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
Person.virtual('email.primary').get(function () {
|
|
153
|
+
var primary = _.find(this.contact.email, function(email) {
|
|
154
|
+
return email.primary;
|
|
155
|
+
});
|
|
156
|
+
return primary ? primary.email : '';
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
Person.virtual('phone.primary').get(function () {
|
|
160
|
+
var primary = _.find(this.contact.phone, function(phone) {
|
|
161
|
+
return phone.primary;
|
|
162
|
+
});
|
|
163
|
+
return primary ? primary.number : '';
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
Person.virtual('interactions.count').get(function () {
|
|
167
|
+
|
|
168
|
+
// handle missing doc data
|
|
169
|
+
this.sources = this.sources || [];
|
|
170
|
+
|
|
171
|
+
var calendarCount = 0;
|
|
172
|
+
var emailCount = 0;
|
|
173
|
+
|
|
174
|
+
_.each(this.sources, function(source) {
|
|
175
|
+
|
|
176
|
+
// Only count interactions for the current customer
|
|
177
|
+
// A universal interaction count isn't useful
|
|
178
|
+
var cid = mongoose.Types.ObjectId.isValid(source.customer) ? source.customer : source.customer._id;
|
|
179
|
+
|
|
180
|
+
if (cid.toString() == config.CUSTOMER_ID) {
|
|
181
|
+
source.interactions = source.interactions || {};
|
|
182
|
+
calendarCount += source.interactions.calendar || 0;
|
|
183
|
+
emailCount += source.interactions.email || 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return calendarCount + emailCount;
|
|
189
|
+
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
Person.virtual('allNotes').get(function () {
|
|
193
|
+
|
|
194
|
+
// combine direct person notes and notes from events this person attended
|
|
195
|
+
// sort reverse chronologically
|
|
196
|
+
|
|
197
|
+
var personNotes = this.notes || [];
|
|
198
|
+
personNotes = _.compact(personNotes);
|
|
199
|
+
|
|
200
|
+
var eventNotes = _.pluck(this.calendarEventSummaries || [], 'notes');
|
|
201
|
+
eventNotes = _.flatten(eventNotes);
|
|
202
|
+
eventNotes = _.compact(eventNotes);
|
|
203
|
+
|
|
204
|
+
var result = _.sortBy(personNotes.concat(eventNotes), 'createdOn');
|
|
205
|
+
|
|
206
|
+
return result.reverse();
|
|
207
|
+
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
/////////////
|
|
211
|
+
|
|
212
|
+
Person.methods.addMerged = function(id) {
|
|
213
|
+
if (this.merged.indexOf(id) == -1) {
|
|
214
|
+
this.merged.push(id);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
Person.methods.deletePerson = function() {
|
|
223
|
+
this.deleted = true;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
Person.methods.relatePerson = function(id) {
|
|
227
|
+
this.related.push(id);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
Person.methods.addChairPos = function(chairObj, orgId) {
|
|
231
|
+
|
|
232
|
+
var chair = {
|
|
233
|
+
company: orgId,
|
|
234
|
+
start: ''
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
switch(chairObj.num) {
|
|
238
|
+
case 'chair1':
|
|
239
|
+
this.chairs.first.push(chair);
|
|
240
|
+
break;
|
|
241
|
+
|
|
242
|
+
case 'chair2':
|
|
243
|
+
this.chairs.second.push(chair);
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
Person.methods.removeChair = function(chairObj, orgId) {
|
|
250
|
+
|
|
251
|
+
var field = 'chairs.first';
|
|
252
|
+
switch(chairObj.num) {
|
|
253
|
+
case 'chair1':
|
|
254
|
+
this.chairs.first = _.filter(this.chairs.first, function(chair) {
|
|
255
|
+
var id = utils.isValidObjectId(chair.company.toString()) ? chair.company.toString() : chair.company.id.toString();
|
|
256
|
+
return id != orgId;
|
|
257
|
+
});
|
|
258
|
+
break;
|
|
259
|
+
|
|
260
|
+
case 'chair2':
|
|
261
|
+
field = 'chairs.second';
|
|
262
|
+
this.chairs.second = _.filter(this.chairs.second, function(chair) {
|
|
263
|
+
var id = utils.isValidObjectId(chair.company.toString()) ? chair.company.toString() : chair.company.id.toString();
|
|
264
|
+
return chair.company.id != orgId;
|
|
265
|
+
});
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if(chairObj.val && chairObj.val == "") {
|
|
270
|
+
this.previous.push({
|
|
271
|
+
field: field,
|
|
272
|
+
value: {
|
|
273
|
+
company: orgId
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
/************* ACCESS **********************/
|
|
281
|
+
|
|
282
|
+
Person.methods.funds = function() {
|
|
283
|
+
return getActiveFunds(this);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
Person.methods.isSubscriber = function() {
|
|
287
|
+
return this.subscriptions && this.subscriptions.length >= 1;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
////////////
|
|
291
|
+
|
|
292
|
+
// search for people based on fields provided
|
|
293
|
+
// Data format, everything is optional
|
|
294
|
+
//{
|
|
295
|
+
// emails: [],
|
|
296
|
+
// phones: [],
|
|
297
|
+
// name: {
|
|
298
|
+
// first: '',
|
|
299
|
+
// last: '',
|
|
300
|
+
// full: ''
|
|
301
|
+
// },
|
|
302
|
+
// rawQuery: ''
|
|
303
|
+
//}
|
|
304
|
+
// first and last names are prioritized over full
|
|
305
|
+
// full, if used, is tokenized into first and last
|
|
306
|
+
// fuzzy searches within strings, e.g., searching for "Drew" will match "Andrew"
|
|
307
|
+
// non-fuzzy does a full-string match, e.g., "Andrew" will match "Andrew" but "Drew" will not
|
|
308
|
+
// both fuzzy and non are case-insensitive
|
|
309
|
+
// fuzzy only applies to name, not email or phone
|
|
310
|
+
// related people are removed from top-level results
|
|
311
|
+
Person.statics.search = function (data, options, cb) {
|
|
312
|
+
|
|
313
|
+
// validate input
|
|
314
|
+
if (!cb) throw new Error('cb is required');
|
|
315
|
+
if (!data || (!data.emails && !data.phones && !data.name)) return cb(null, []);
|
|
316
|
+
|
|
317
|
+
var self = this;
|
|
318
|
+
var query;
|
|
319
|
+
|
|
320
|
+
var buildQuery = function buildQuery(data, options) {
|
|
321
|
+
|
|
322
|
+
var cleanQuery = function cleanQuery(query) {
|
|
323
|
+
|
|
324
|
+
// "and" isn't used, so remove it
|
|
325
|
+
if (query['$and'].length == 0) {
|
|
326
|
+
delete query['$and'];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// "and" is only used for name, which is one more thing to "or" so move it to "or"
|
|
330
|
+
// note that changes may be needed if "and" is used for other things as well
|
|
331
|
+
else {
|
|
332
|
+
query['$or'].push({ '$and': query['$and'] });
|
|
333
|
+
delete query['$and'];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return query;
|
|
337
|
+
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
var getNicknames = function getNicknames(word, dictionary) {
|
|
341
|
+
var synonyms = dictionary[word.toLowerCase()];
|
|
342
|
+
if (!synonyms) return [word];
|
|
343
|
+
return synonyms;
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
var oneWordFullNameQuery = function oneWordFullNameQuery(data) {
|
|
347
|
+
var fullNameContainsSpace = data.name.full.indexOf(' ') >= 0;
|
|
348
|
+
var result = data.name.full.length >= 1 && fullNameContainsSpace == false && data.original.name.first.length == 0 && data.original.name.last.length == 0;
|
|
349
|
+
return result;
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
var prepareDictionary = function prepareDictionary() {
|
|
353
|
+
|
|
354
|
+
var dictionary = require('../dictionaries/nicknames.json');
|
|
355
|
+
|
|
356
|
+
var dict = {};
|
|
357
|
+
|
|
358
|
+
// Expand synonyms dictionary for caching
|
|
359
|
+
_.each(dictionary, function(synonyms, word) {
|
|
360
|
+
var key = word.toLowerCase();
|
|
361
|
+
dict[key] = [word].concat(synonyms);
|
|
362
|
+
// Add values as keys for back-matching
|
|
363
|
+
dict[key].forEach(function(synonym) {
|
|
364
|
+
var synonymKey = synonym.toLowerCase();
|
|
365
|
+
if (!dict[synonymKey]) dict[synonymKey] = [word];
|
|
366
|
+
dict[synonymKey] = _.uniq(dict[synonymKey].concat(dict[key]));
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
return dict;
|
|
371
|
+
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// prepare query
|
|
375
|
+
var query = {};
|
|
376
|
+
query['$or'] = []; // these will be added dynamically from data parameter
|
|
377
|
+
query['$and'] = []; // these will be added dynamically from data parameter
|
|
378
|
+
query['deleted'] = { $ne: true }; // always include this
|
|
379
|
+
|
|
380
|
+
var dictionary = prepareDictionary();
|
|
381
|
+
var nicknames = getNicknames(data.name.first, dictionary);
|
|
382
|
+
var firstEscaped = escapeStringRegexp(data.name.first);
|
|
383
|
+
var firstRegExp = options.fuzzy ? new RegExp(firstEscaped, 'i') : new RegExp('^' + firstEscaped + '$', 'i');
|
|
384
|
+
var lastEscaped = escapeStringRegexp(data.name.last);
|
|
385
|
+
var lastRegExp = options.fuzzy ? new RegExp(lastEscaped, 'i') : new RegExp('^' + lastEscaped + '$', 'i');
|
|
386
|
+
var key = options.andFirstLast && data.name.first && data.name.last ? '$and' : '$or';
|
|
387
|
+
|
|
388
|
+
// if we only have one word, search both first and last
|
|
389
|
+
if (oneWordFullNameQuery(data)) {
|
|
390
|
+
|
|
391
|
+
query['$or'].push({ 'name.first': firstRegExp });
|
|
392
|
+
query['$or'].push({ 'name.last': firstRegExp });
|
|
393
|
+
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// options say to "and", rather than "or", first and last names
|
|
397
|
+
else if (options.andFirstLast && data.name.first && data.name.last) {
|
|
398
|
+
|
|
399
|
+
_.each(nicknames, function(nickname) {
|
|
400
|
+
var escapedNickname = escapeStringRegexp(nickname);
|
|
401
|
+
var nicknameRegExp = options.fuzzy ? new RegExp(escapedNickname, 'i') : new RegExp('^' + escapedNickname + '$', 'i');
|
|
402
|
+
query['$or'].push({ 'name.first': nicknameRegExp, 'name.last': lastRegExp });
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
|
|
408
|
+
// include first unless options say not to
|
|
409
|
+
if (data.name.first && options.orLastOnly == false) {
|
|
410
|
+
|
|
411
|
+
_.each(nicknames, function (nickname) {
|
|
412
|
+
var escapedNickname = escapeStringRegexp(nickname);
|
|
413
|
+
var nicknameRegExp = options.fuzzy ? new RegExp(escapedNickname, 'i') : new RegExp('^' + escapedNickname + '$', 'i');
|
|
414
|
+
query[key].push({'name.first': nicknameRegExp});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// always include last if it's provided
|
|
420
|
+
if (data.name.last) {
|
|
421
|
+
query[key].push({ 'name.last': lastRegExp });
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// search alias as well
|
|
427
|
+
if (data.name.first || data.name.last) {
|
|
428
|
+
var fullName = escapeStringRegexp((data.name.first + ' ' + data.name.last).trim());
|
|
429
|
+
var aliasRegExp = options.fuzzy ? new RegExp(fullName, 'i') : new RegExp('^' + fullName + '$', 'i');
|
|
430
|
+
query['$or'].push({ 'aliases': aliasRegExp });
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// conditionally include emails and phones
|
|
434
|
+
if (data.emails) query['$or'].push({'contact.email.email': { $in : data.emails }});
|
|
435
|
+
if (data.phones) query['$or'].push({'contact.phone.number': { $in : data.phones }});
|
|
436
|
+
|
|
437
|
+
query = cleanQuery(query);
|
|
438
|
+
|
|
439
|
+
return query;
|
|
440
|
+
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
var prepData = function prepData(data) {
|
|
444
|
+
|
|
445
|
+
// add raw query to email query if it is an email
|
|
446
|
+
var addIfEmail = function addIfEmail(data) {
|
|
447
|
+
|
|
448
|
+
if (validator.isEmail(data.rawQuery)) {
|
|
449
|
+
if (data.emails.indexOf(data.rawQuery) == -1) {
|
|
450
|
+
data.emails.push(data.rawQuery);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return data;
|
|
455
|
+
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// add raw query to phone query if it is a phone
|
|
459
|
+
var addIfPhone = function addIfPhone(data) {
|
|
460
|
+
|
|
461
|
+
var pn = new PhoneNumber(data.rawQuery, 'US');
|
|
462
|
+
|
|
463
|
+
if (pn.isValid()) {
|
|
464
|
+
if (data.phones.indexOf(data.rawQuery) == -1) {
|
|
465
|
+
data.phones.push(data.rawQuery);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return data;
|
|
470
|
+
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// remove name from data if it's actually an email
|
|
474
|
+
var removeIfEmail = function removeIfEmail(data) {
|
|
475
|
+
|
|
476
|
+
if (validator.isEmail(data.name.full)) data.name.full = '';
|
|
477
|
+
if (validator.isEmail(data.name.first)) data.name.first = '';
|
|
478
|
+
if (validator.isEmail(data.name.last)) data.name.last = '';
|
|
479
|
+
|
|
480
|
+
return data;
|
|
481
|
+
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
// remove name from data if it's actually a phone number
|
|
485
|
+
var removeIfPhone = function removeIfPhone(data) {
|
|
486
|
+
|
|
487
|
+
var pnFull = new PhoneNumber(data.name.full, 'US');
|
|
488
|
+
if (pnFull.isValid()) data.name.full = '';
|
|
489
|
+
|
|
490
|
+
var pnFirst = new PhoneNumber(data.name.first, 'US');
|
|
491
|
+
if (pnFirst.isValid()) data.name.first = '';
|
|
492
|
+
|
|
493
|
+
var pnLast = new PhoneNumber(data.name.last, 'US');
|
|
494
|
+
if (pnLast.isValid()) data.name.last = '';
|
|
495
|
+
|
|
496
|
+
return data;
|
|
497
|
+
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
// break full name into parts
|
|
501
|
+
var tokenizeName = function tokenizeName(data) {
|
|
502
|
+
|
|
503
|
+
var parts = [];
|
|
504
|
+
|
|
505
|
+
try { parts = data.name.full.toString().split(' '); }
|
|
506
|
+
catch (err) { return data; }
|
|
507
|
+
|
|
508
|
+
// LIMITATION: Doesn't handle 2+ word first names, i.e., Bobbi Sue is fucked
|
|
509
|
+
|
|
510
|
+
// if there is at least one part, use the first, but don't overwrite
|
|
511
|
+
if (parts.length >= 1 && !data.name.first) data.name.first = parts[0].trim();
|
|
512
|
+
|
|
513
|
+
// if there are at least two parts, join all but the first, but don't overwrite
|
|
514
|
+
if (parts.length >= 2 && !data.name.last) data.name.last = parts.slice(1).join(' ');
|
|
515
|
+
|
|
516
|
+
return data;
|
|
517
|
+
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// save the original name
|
|
521
|
+
data.original =
|
|
522
|
+
data.original = {
|
|
523
|
+
name: {
|
|
524
|
+
first: data.name && data.name.first || '',
|
|
525
|
+
last: data.name && data.name.last || ''
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
// make sure each field exists, even if empty, so we don't have to constantly check
|
|
530
|
+
data.emails = data.emails || [];
|
|
531
|
+
data.phones = data.phones || [];
|
|
532
|
+
data.name = data.name || {};
|
|
533
|
+
data.name.first = data.name.first || '';
|
|
534
|
+
data.name.last = data.name.last || '';
|
|
535
|
+
data.name.full = data.name.full || '';
|
|
536
|
+
data.rawQuery = data.rawQuery || '';
|
|
537
|
+
|
|
538
|
+
data = addIfEmail(data);
|
|
539
|
+
data = removeIfEmail(data);
|
|
540
|
+
data = addIfPhone(data);
|
|
541
|
+
data = removeIfPhone(data);
|
|
542
|
+
data = tokenizeName(data);
|
|
543
|
+
|
|
544
|
+
return data;
|
|
545
|
+
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
// All entities are searched initially, including entities that are listed as related to other entities
|
|
549
|
+
// Remove related entities from top-level results so they only come up as related
|
|
550
|
+
var removeRelatedFromTopLevel = function(items) {
|
|
551
|
+
|
|
552
|
+
var getRelatedIds = function getRelatedIds(items) {
|
|
553
|
+
|
|
554
|
+
var result = [];
|
|
555
|
+
|
|
556
|
+
_.each(items, function(item) {
|
|
557
|
+
if (item.related.length >= 1) {
|
|
558
|
+
var ids = _.pluck(item.related, 'id');
|
|
559
|
+
result = result.concat(ids);
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
return _.uniq(result);
|
|
564
|
+
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
var relatedIds = getRelatedIds(items);
|
|
568
|
+
items = _.reject(items, function(item) {
|
|
569
|
+
return relatedIds.indexOf(item.id) >= 0;
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
return items;
|
|
573
|
+
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
var defaultOptions = {
|
|
577
|
+
|
|
578
|
+
// accept partial matches
|
|
579
|
+
fuzzy: true,
|
|
580
|
+
|
|
581
|
+
// use 'and' clause, rather than 'or', between first and last
|
|
582
|
+
// note this trumps orLastOnly
|
|
583
|
+
andFirstLast: false,
|
|
584
|
+
|
|
585
|
+
// when not 'anding' first and last, query on last name but not first
|
|
586
|
+
// note this is trumped by andFirstLast
|
|
587
|
+
orLastOnly: true,
|
|
588
|
+
|
|
589
|
+
removeRelatedFromTopLevel: true
|
|
590
|
+
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// combine provided and default options
|
|
594
|
+
options = utils.setDefaults(options, defaultOptions);
|
|
595
|
+
|
|
596
|
+
// need to be admin or provide investor id
|
|
597
|
+
if (!options.admin && !options.investor) return cb(null, []);
|
|
598
|
+
|
|
599
|
+
data = prepData(data);
|
|
600
|
+
|
|
601
|
+
query = buildQuery(data, options);
|
|
602
|
+
|
|
603
|
+
self
|
|
604
|
+
.find(query)
|
|
605
|
+
.select('avatarUrl name title related')
|
|
606
|
+
.exec(function(err, people) {
|
|
607
|
+
|
|
608
|
+
if (err) return cb(err, null);
|
|
609
|
+
else if (!people) return cb(null, null);
|
|
610
|
+
else {
|
|
611
|
+
|
|
612
|
+
if (options.removeRelatedFromTopLevel) people = removeRelatedFromTopLevel(people);
|
|
613
|
+
|
|
614
|
+
people = _.sortBy(people, function(person) { return person.name.full; });
|
|
615
|
+
|
|
616
|
+
return cb(null, people);
|
|
617
|
+
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
Person.statics.findDupes = function(person, options, cb) {
|
|
625
|
+
|
|
626
|
+
if (!cb) throw new Error('cb is required');
|
|
627
|
+
if (!person) return cb(null, []);
|
|
628
|
+
|
|
629
|
+
var emails = [];
|
|
630
|
+
if (person.contact && person.contact.email) {
|
|
631
|
+
// pluck emails
|
|
632
|
+
emails = _.map(person.contact.email, function(email) {
|
|
633
|
+
return email.email;
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
var phones = [];
|
|
638
|
+
if (person.contact && person.contact.phone) {
|
|
639
|
+
// pluck and normalize phone numbers
|
|
640
|
+
phones = _.map(person.contact.phone, function(phone) {
|
|
641
|
+
return phoneFormatter.normalize(phone.number);
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Sometimes clients pass in something like { name: 'Jason Reynolds' } rather than { name: { first: 'Jason', last: 'Reynolds' } }
|
|
646
|
+
// Parse it out as a courtesy
|
|
647
|
+
if (!person.name.first && !person.name.last) {
|
|
648
|
+
var fullName = person.name;
|
|
649
|
+
person = {
|
|
650
|
+
name: {
|
|
651
|
+
first: fullName.split(" ")[0],
|
|
652
|
+
last: fullName.split(" ")[1]
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Require names that are at least two characters long
|
|
658
|
+
if (person.name.first.length < 2) person.name.first = '';
|
|
659
|
+
if (person.name.last.length < 2) person.name.last = '';
|
|
660
|
+
|
|
661
|
+
this.search({
|
|
662
|
+
emails: emails,
|
|
663
|
+
phones: phones,
|
|
664
|
+
name: person.name
|
|
665
|
+
}, options, function(err, people) {
|
|
666
|
+
|
|
667
|
+
if (err) return cb(err, null);
|
|
668
|
+
if (!people) return cb(null, null);
|
|
669
|
+
|
|
670
|
+
// exclude passed in person from results
|
|
671
|
+
people = _.reject(people, function(p) {
|
|
672
|
+
return p.id == person.id;
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
return cb(null, people);
|
|
676
|
+
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
Person.statics.stats = function (cb) {
|
|
682
|
+
this.collection.stats(cb);
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
Person.statics.getById = function (id, cb) {
|
|
686
|
+
|
|
687
|
+
this
|
|
688
|
+
.findById(id)
|
|
689
|
+
.populate('account')
|
|
690
|
+
.populate('chairs.first.company')
|
|
691
|
+
.populate('chairs.second.company')
|
|
692
|
+
.populate('notes')
|
|
693
|
+
.populate('related')
|
|
694
|
+
.populate('sources.person')
|
|
695
|
+
.exec(function (err, person) {
|
|
696
|
+
|
|
697
|
+
if (err) return cb(err, null);
|
|
698
|
+
else if (!person) return cb(null, null);
|
|
699
|
+
|
|
700
|
+
person.sources = _.sortBy(person.sources, function(source) {
|
|
701
|
+
return source.interactions.calendar + source.interactions.email;
|
|
702
|
+
}).reverse();
|
|
703
|
+
|
|
704
|
+
return cb(null, person);
|
|
705
|
+
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
Person.statics.getByIdCustomer = function (id, customer, cb) {
|
|
711
|
+
|
|
712
|
+
// this would speed it up
|
|
713
|
+
// still need a way to optionally return all though
|
|
714
|
+
//.findById(id, { 'calendarEventSummaries': { $slice: 5 } })
|
|
715
|
+
|
|
716
|
+
this
|
|
717
|
+
.findById(id)
|
|
718
|
+
.select('-previous')
|
|
719
|
+
.populate('calendarEventSummaries.attendees.external', 'avatarUrl name title')
|
|
720
|
+
.populate('calendarEventSummaries.attendees.internal', 'avatarUrl name title')
|
|
721
|
+
.populate('calendarEventSummaries.notes')
|
|
722
|
+
.populate('chairs.first.company', 'logoUrl name')
|
|
723
|
+
.populate('chairs.second.company', 'logoUrl name')
|
|
724
|
+
.populate('sources.person', 'avatarUrl name title')
|
|
725
|
+
.deepPopulate([
|
|
726
|
+
'notes.createdBy',
|
|
727
|
+
'calendarEventSummaries.notes.createdBy'
|
|
728
|
+
], {
|
|
729
|
+
populate: {
|
|
730
|
+
'notes.createdBy': {select: 'avatarUrl name'},
|
|
731
|
+
'calendarEventSummaries.notes.createdBy': { select: 'avatarUrl name' }
|
|
732
|
+
}
|
|
733
|
+
})
|
|
734
|
+
.exec(function (err, person) {
|
|
735
|
+
|
|
736
|
+
if (err) return cb(err, null);
|
|
737
|
+
else if (!person) return cb(null, null);
|
|
738
|
+
|
|
739
|
+
person.sources = _.sortBy(person.sources, function(source) {
|
|
740
|
+
return source.interactions.calendar + source.interactions.email;
|
|
741
|
+
}).reverse();
|
|
742
|
+
|
|
743
|
+
// note this gets filtered by customer in post init hook
|
|
744
|
+
// sort descending, i.e., reverse chronologically
|
|
745
|
+
person.calendarEventSummaries = _.sortBy(person.calendarEventSummaries, function(summary) {
|
|
746
|
+
return summary.startTime;
|
|
747
|
+
}).reverse();
|
|
748
|
+
|
|
749
|
+
return cb(null, person);
|
|
750
|
+
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
Person.statics.getByIdNoPopulate = function (id, cb) {
|
|
756
|
+
|
|
757
|
+
this
|
|
758
|
+
.findById(id)
|
|
759
|
+
.exec(cb);
|
|
760
|
+
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
Person.statics.getByIds = function (ids, cb) {
|
|
764
|
+
this
|
|
765
|
+
.find({ '_id': { $in : ids }, 'deleted': {$ne: true} })
|
|
766
|
+
.exec(cb);
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
// Retrieve any person matching an email in the emails array parameter
|
|
770
|
+
Person.statics.findByEmails = function (emails, cb) {
|
|
771
|
+
|
|
772
|
+
// sort is done post retrieval because query sort is done pre-decryption
|
|
773
|
+
|
|
774
|
+
this
|
|
775
|
+
.find({ 'contact.email.email': { $in : emails }, 'deleted': {$ne: true}})
|
|
776
|
+
.select('name avatarUrl')
|
|
777
|
+
.exec(function (err, people) {
|
|
778
|
+
cb(err, _.sortBy(people, function(person) { return person.name.full; }));
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
// Retrieve any person matching an email in the emails array parameter
|
|
784
|
+
Person.statics.findByEmailsSkinny = function (emails, cb) {
|
|
785
|
+
this
|
|
786
|
+
.find({ 'contact.email.email': { $in : emails }, 'deleted': {$ne: true}})
|
|
787
|
+
.exec(cb);
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
// TODO - make dynamic
|
|
791
|
+
Person.statics.getPartners = function (cb) {
|
|
792
|
+
|
|
793
|
+
var partnerEmails = [
|
|
794
|
+
'john@ffvc.com',
|
|
795
|
+
'adam@ffvc.com',
|
|
796
|
+
'alex@ffvc.com',
|
|
797
|
+
'david@ffvc.com',
|
|
798
|
+
'michael@ffvc.com',
|
|
799
|
+
'ryan@ffvc.com',
|
|
800
|
+
'herb@ffvc.com',
|
|
801
|
+
'paul@ffvc.com'
|
|
802
|
+
];
|
|
803
|
+
|
|
804
|
+
this
|
|
805
|
+
.find({ 'contact.email.email': { $in: partnerEmails }, 'deleted': { $ne: true } } )
|
|
806
|
+
.populate('account boards.company chairs.first.company chairs.second.company')
|
|
807
|
+
.exec(cb);
|
|
808
|
+
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
Person.statics.findByProviderEventId = function (id, cb) {
|
|
812
|
+
this
|
|
813
|
+
.find({ 'calendarEventSummaries.providerEventId': id })
|
|
814
|
+
.exec(cb);
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
Person.statics.findBySlug = function (slug, cb) {
|
|
818
|
+
this
|
|
819
|
+
.find({ slug: new RegExp(slug, 'i'), 'deleted': {$ne: true} })
|
|
820
|
+
.exec(cb);
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
Person.statics.findBySlugs = function (slugs, cb) {
|
|
824
|
+
this
|
|
825
|
+
.find({ 'slug': { $in : slugs }, 'deleted': {$ne: true} })
|
|
826
|
+
.exec(cb);
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
Person.statics.findByUniqueInfo = function (email, cb) {
|
|
830
|
+
|
|
831
|
+
// useful for not inserting dupes
|
|
832
|
+
// todo: expand this to check other fields
|
|
833
|
+
|
|
834
|
+
this.findOne({
|
|
835
|
+
$or: [
|
|
836
|
+
{ 'contact.email': { $elemMatch: {'email': crypto.encrypt(email)} }},
|
|
837
|
+
{ 'contact.email': { $elemMatch: {'email': email} }}
|
|
838
|
+
]})
|
|
839
|
+
.populate('account boards.company leadership.company chairs.first.company chairs.second.company')
|
|
840
|
+
.exec(cb);
|
|
841
|
+
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
Person.statics.listAllPeople = function (cb) {
|
|
845
|
+
|
|
846
|
+
// sort is done post retrieval because query sort is done pre-decryption
|
|
847
|
+
|
|
848
|
+
var query = this.find({'deleted': {$ne: true}});
|
|
849
|
+
query.populate('account chairs.first.company chairs.second.company');
|
|
850
|
+
query.exec(function (err, people) {
|
|
851
|
+
cb(err, _.sortBy(people, function(person) { return person.name.full; }));
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
Person.statics.listMostRecentPeople = function (cb) {
|
|
857
|
+
|
|
858
|
+
// sort is done post retrieval because query sort is done pre-decryption
|
|
859
|
+
|
|
860
|
+
this
|
|
861
|
+
.find({'deleted': {$ne: true}})
|
|
862
|
+
.sort({'_id': 1})
|
|
863
|
+
.limit(100)
|
|
864
|
+
.exec(function (err, people) {
|
|
865
|
+
cb(err, _.sortBy(people, function(person) { return person.name.full; }));
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
Person.statics.listPage = function (skip, cb) {
|
|
871
|
+
|
|
872
|
+
this
|
|
873
|
+
.find({'deleted': {$ne: true}})
|
|
874
|
+
.sort({'name.first':1, 'name.last':1})
|
|
875
|
+
.skip(skip)
|
|
876
|
+
.limit(100)
|
|
877
|
+
.exec(cb);
|
|
878
|
+
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
Person.statics.listAllPeopleNoPop = function (cb) {
|
|
882
|
+
|
|
883
|
+
// sort is done post retrieval because query sort is done pre-decryption
|
|
884
|
+
|
|
885
|
+
var query = this.find({'deleted': {$ne: true}});
|
|
886
|
+
query.exec(function (err, people) {
|
|
887
|
+
cb(err, _.sortBy(people, function(person) { return person.name.full; }));
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
Person.statics.listAllUnverifiedPeople = function (cb) {
|
|
893
|
+
|
|
894
|
+
this
|
|
895
|
+
.find({'status.verified': false, 'deleted': {$ne: true}})
|
|
896
|
+
.sort({'_id': 1})
|
|
897
|
+
.limit(100)
|
|
898
|
+
.exec(function (err, people) {
|
|
899
|
+
cb(err, _.sortBy(people, function(person) { return person.name.full; }));
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
Person.statics.listAllFlaggedPeople = function (cb) {
|
|
905
|
+
|
|
906
|
+
this
|
|
907
|
+
.find({'flagged.resolved': false, 'flagged.text': { $ne: '' }, 'deleted': {$ne: true}})
|
|
908
|
+
.exec(function (err, people) {
|
|
909
|
+
people = _.sortBy(people, function(person){
|
|
910
|
+
var timestamp = new Date(person.flagged.on);
|
|
911
|
+
timestamp = timestamp.getTime();
|
|
912
|
+
return timestamp;
|
|
913
|
+
});
|
|
914
|
+
people = people.reverse();
|
|
915
|
+
|
|
916
|
+
cb(err, people);
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
Person.statics.listAllPeopleWithoutAccounts = function (cb) {
|
|
922
|
+
|
|
923
|
+
// sort is done post retrieval because query sort is done pre-decryption
|
|
924
|
+
|
|
925
|
+
this
|
|
926
|
+
.find({account: {$exists: false}, 'deleted': {$ne: true}})
|
|
927
|
+
.exec(function (err, people) {
|
|
928
|
+
cb(err, _.sortBy(people, function(person) { return person.name.full; }));
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
Person.statics.listForSubscription = function (subscriptionId, cb) {
|
|
934
|
+
|
|
935
|
+
this
|
|
936
|
+
.find({ 'subscriptions.id': subscriptionId, 'deleted': {$ne: true}})
|
|
937
|
+
.exec(function(err, people) {
|
|
938
|
+
cb(err, people);
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
Person.statics.addChair = function(personId, chairPos, companyId, cb) {
|
|
944
|
+
|
|
945
|
+
this.findById(personId).exec(function(err, person) {
|
|
946
|
+
|
|
947
|
+
var chairObj = {
|
|
948
|
+
company: companyId,
|
|
949
|
+
start: ''
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
switch(chairPos) {
|
|
953
|
+
case 'first':
|
|
954
|
+
person.chairs.first.push(chairObj);
|
|
955
|
+
break;
|
|
956
|
+
|
|
957
|
+
case 'second':
|
|
958
|
+
person.chairs.second.push(chairObj);
|
|
959
|
+
break;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
person.save(cb);
|
|
963
|
+
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
//same function for flag resolved and unresolved... all based on true|false resolved arg
|
|
969
|
+
Person.statics.flag = function(personId, resolved, text, username, cb) {
|
|
970
|
+
|
|
971
|
+
this.findById(personId).exec(function(err, person) {
|
|
972
|
+
|
|
973
|
+
if (!person) return cb(new Error('person not found'), null);
|
|
974
|
+
|
|
975
|
+
person.flagged.resolved = resolved;
|
|
976
|
+
person.flagged.text = text;
|
|
977
|
+
person.flagged.by = username;
|
|
978
|
+
person.flagged.on = Date.now();
|
|
979
|
+
|
|
980
|
+
person.save(cb);
|
|
981
|
+
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
Person.statics.setCustomerCompanyIds = function(ids) {
|
|
987
|
+
customerCompanyIds = ids;
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
Person.statics.upsert = function(currentPerson, username, cb) {
|
|
991
|
+
|
|
992
|
+
if (!currentPerson) { return cb(new Error('Current Person is required'), null); }
|
|
993
|
+
if (!username) { return cb(new Error('Username is required'), null); }
|
|
994
|
+
|
|
995
|
+
currentPerson.entered = {
|
|
996
|
+
by: username,
|
|
997
|
+
on: new Date()
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
currentPerson.save(cb);
|
|
1001
|
+
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
Person.statics.addNote = function(personId, creatorPersonId, text, cb) {
|
|
1005
|
+
|
|
1006
|
+
Note.createForModel({
|
|
1007
|
+
createdBy: creatorPersonId,
|
|
1008
|
+
text: text
|
|
1009
|
+
}, this, personId, cb);
|
|
1010
|
+
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
Person.statics.deleteNote = function(noteId, cb) {
|
|
1014
|
+
|
|
1015
|
+
// Delete the note itself along with any references on people
|
|
1016
|
+
|
|
1017
|
+
var self = this;
|
|
1018
|
+
|
|
1019
|
+
var removePersonNoteReferences = function removePersonNoteReferences(callback) {
|
|
1020
|
+
self.update({}, {
|
|
1021
|
+
$pull: { 'notes' : [noteId] }
|
|
1022
|
+
}, callback);
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
var removeEventNoteReferences = function removeEventNoteReferences(callback) {
|
|
1026
|
+
self.update({
|
|
1027
|
+
'calendarEventSummaries.notes': noteId
|
|
1028
|
+
}, {
|
|
1029
|
+
$pull: { 'calendarEventSummaries.$.notes' : [noteId] }
|
|
1030
|
+
}, callback);
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
async.series([
|
|
1034
|
+
Note.delete.bind(Note, noteId),
|
|
1035
|
+
removePersonNoteReferences,
|
|
1036
|
+
removeEventNoteReferences
|
|
1037
|
+
], function(err, results) {
|
|
1038
|
+
return cb(err, null);
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
///////////////
|
|
1044
|
+
|
|
1045
|
+
// Person.pre('save', function(next) {
|
|
1046
|
+
// var self = this;
|
|
1047
|
+
// var PersonHistory = mongoose.model('PersonHistory');
|
|
1048
|
+
// var snapshot = new PersonHistory();
|
|
1049
|
+
// snapshot.create(self, 'jason', function(err, result) {
|
|
1050
|
+
// if (err) {} // todo: alert admin to fix; ok to proceed;
|
|
1051
|
+
// return next();
|
|
1052
|
+
// });
|
|
1053
|
+
// });
|
|
1054
|
+
|
|
1055
|
+
Person.post('remove', function(doc) {
|
|
1056
|
+
// Account.person
|
|
1057
|
+
// Investor.people
|
|
1058
|
+
// Investor.formerPeople
|
|
1059
|
+
// LimitedPartner.person
|
|
1060
|
+
// Organization.people.person
|
|
1061
|
+
// Person.merged
|
|
1062
|
+
// Person.related
|
|
1063
|
+
// Person.calendarEventSummaries.attendees.internal
|
|
1064
|
+
// Person.calendarEventSummaries.attendees.external
|
|
1065
|
+
// PortfolioCompany.chairs.first
|
|
1066
|
+
// PortfolioCompany.chairs.second
|
|
1067
|
+
// PortfolioCompany.boards
|
|
1068
|
+
// PortfolioCompany.leadership.person
|
|
1069
|
+
// PortfolioCompany.people.person
|
|
1070
|
+
// PortfolioCompany.formerPeople
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
var protectCustomerData = function(context) {
|
|
1074
|
+
|
|
1075
|
+
var CUSTOMER_ID = config.CUSTOMER_ID;
|
|
1076
|
+
|
|
1077
|
+
var protectCalendarEventData = function protectCalendarEventData(person) {
|
|
1078
|
+
|
|
1079
|
+
if (CUSTOMER_ID == 'GLOBAL_PROCESS') return;
|
|
1080
|
+
|
|
1081
|
+
person.calendarEventSummaries = _.reject(person.calendarEventSummaries, function(event) {
|
|
1082
|
+
return event.totemCustomerId != CUSTOMER_ID;
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
var protectSources = function protectSources(person) {
|
|
1088
|
+
|
|
1089
|
+
if (CUSTOMER_ID == 'GLOBAL_PROCESS') return;
|
|
1090
|
+
|
|
1091
|
+
person.sources = _.reject(person.sources, function(source) {
|
|
1092
|
+
var customerId = mongoose.Types.ObjectId.isValid(source.customer) ? source.customer : source.customer._id;
|
|
1093
|
+
return customerId.toString() != CUSTOMER_ID.toString();
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
protectCalendarEventData(context);
|
|
1099
|
+
protectSources(context);
|
|
1100
|
+
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
Person.pre('save', function(next) {
|
|
1104
|
+
|
|
1105
|
+
var self = this;
|
|
1106
|
+
|
|
1107
|
+
// Reset counters so we can calc from scratch
|
|
1108
|
+
_.each(self.sources, function(source) {
|
|
1109
|
+
source.interactions.calendar = 0;
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
_.each(self.calendarEventSummaries, function(ces) {
|
|
1113
|
+
|
|
1114
|
+
var customerId = ces.totemCustomerId;
|
|
1115
|
+
|
|
1116
|
+
// Each attendee from the customer is a source
|
|
1117
|
+
_.each(ces.attendees.internal, function(attendee) {
|
|
1118
|
+
|
|
1119
|
+
var aid = mongoose.Types.ObjectId.isValid(attendee) ? attendee : attendee._id;
|
|
1120
|
+
|
|
1121
|
+
// Check if this attendee has already been added as a source
|
|
1122
|
+
var match = _.find(self.sources, function(source) {
|
|
1123
|
+
// It's a match if both person and customer ids are a match
|
|
1124
|
+
var pid = mongoose.Types.ObjectId.isValid(source.person) ? source.person : source.person._id;
|
|
1125
|
+
var cid = mongoose.Types.ObjectId.isValid(source.customer) ? source.customer : source.customer._id;
|
|
1126
|
+
return pid.toString() == aid.toString() && cid.toString() == customerId.toString();
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
// If source already added, increment counter
|
|
1130
|
+
if (match) { match.interactions.calendar += 1; }
|
|
1131
|
+
|
|
1132
|
+
// Otherwise add the source
|
|
1133
|
+
else {
|
|
1134
|
+
self.sources.push({
|
|
1135
|
+
person: aid,
|
|
1136
|
+
customer: customerId,
|
|
1137
|
+
interactions: { calendar: 1 }
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
next();
|
|
1146
|
+
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
Person.post('init', function() {
|
|
1150
|
+
protectCustomerData(this);
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
var deepPopulate = require('mongoose-deep-populate')(mongoose);
|
|
1154
|
+
Person.plugin(deepPopulate);
|
|
1155
|
+
|
|
1156
|
+
Person.set('autoIndex', true);
|
|
1157
|
+
Person.on('index', function(err) { console.log('error building person indexes: ' + err); });
|
|
1158
|
+
|
|
1159
|
+
Person.set('toJSON', { virtuals: true });
|
|
1160
|
+
|
|
1161
|
+
mongoose.model('Person', Person);
|
|
1162
|
+
|
|
1163
|
+
};
|