@dhyasama/totem-models 12.31.0 → 12.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Financials.js +68 -17
- package/package.json +1 -1
- package/test/Financials.js +60 -0
- package/test/FinancialsSkippedUnit.js +234 -0
package/lib/Financials.js
CHANGED
|
@@ -128,6 +128,12 @@ module.exports = function(mongoose, config) {
|
|
|
128
128
|
group: { type: String, trim: true },
|
|
129
129
|
position: {
|
|
130
130
|
_id: false,
|
|
131
|
+
text: {
|
|
132
|
+
_id: false,
|
|
133
|
+
quote: { type: String, maxlength: 4096 },
|
|
134
|
+
prefix: { type: String, maxlength: 256 },
|
|
135
|
+
suffix: { type: String, maxlength: 256 }
|
|
136
|
+
},
|
|
131
137
|
page: { type: Number },
|
|
132
138
|
bbox: {
|
|
133
139
|
_id: false,
|
|
@@ -173,6 +179,12 @@ module.exports = function(mongoose, config) {
|
|
|
173
179
|
group: { type: String, trim: true },
|
|
174
180
|
position: {
|
|
175
181
|
_id: false,
|
|
182
|
+
text: {
|
|
183
|
+
_id: false,
|
|
184
|
+
quote: { type: String, maxlength: 4096 },
|
|
185
|
+
prefix: { type: String, maxlength: 256 },
|
|
186
|
+
suffix: { type: String, maxlength: 256 }
|
|
187
|
+
},
|
|
176
188
|
page: { type: Number },
|
|
177
189
|
bbox: {
|
|
178
190
|
_id: false,
|
|
@@ -876,10 +888,11 @@ module.exports = function(mongoose, config) {
|
|
|
876
888
|
var query = this.find({
|
|
877
889
|
'snapshots': {
|
|
878
890
|
$elemMatch: {
|
|
891
|
+
'form.0': { $exists: true },
|
|
879
892
|
'notifications.company.initial.sendTo': { $gt: [] },
|
|
880
893
|
'notifications.company.initial.sendOn': { $lte: now },
|
|
881
894
|
'notifications.company.initial.sentOn': null,
|
|
882
|
-
'status': { $nin: ['Pending', 'Completed', 'Archived'] }
|
|
895
|
+
'status': { $nin: ['Pending', 'Completed', 'Archived', 'Skipped'] }
|
|
883
896
|
}
|
|
884
897
|
}
|
|
885
898
|
}).select({
|
|
@@ -889,6 +902,8 @@ module.exports = function(mongoose, config) {
|
|
|
889
902
|
'snapshots.uuid': 1,
|
|
890
903
|
'snapshots.year': 1,
|
|
891
904
|
'snapshots.period': 1,
|
|
905
|
+
'snapshots.form._id': 1,
|
|
906
|
+
'snapshots.status': 1,
|
|
892
907
|
'snapshots.notifications.company.initial.sendTo': 1,
|
|
893
908
|
'snapshots.notifications.company.initial.sendOn': 1,
|
|
894
909
|
'snapshots.notifications.company.initial.sentOn': 1,
|
|
@@ -901,15 +916,20 @@ module.exports = function(mongoose, config) {
|
|
|
901
916
|
|
|
902
917
|
_.each(financial.snapshots, function(snapshot) {
|
|
903
918
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
var
|
|
919
|
+
if (!snapshot.form || snapshot.form.length === 0) return;
|
|
920
|
+
if (_.contains(['Pending', 'Completed', 'Archived', 'Skipped'], snapshot.status)) return;
|
|
921
|
+
var initial = snapshot.notifications && snapshot.notifications.company && snapshot.notifications.company.initial;
|
|
922
|
+
if (!initial) return;
|
|
923
|
+
|
|
924
|
+
var hasSendTo = initial.sendTo && initial.sendTo.length > 0;
|
|
925
|
+
var hasSendOnLessThanNow = initial.sendOn && initial.sendOn < now;
|
|
926
|
+
var hasSentOn = initial.sentOn;
|
|
907
927
|
|
|
908
928
|
var conditions = hasSendTo && hasSendOnLessThanNow && !hasSentOn;
|
|
909
929
|
|
|
910
930
|
if(conditions) {
|
|
911
931
|
|
|
912
|
-
var recipients =
|
|
932
|
+
var recipients = initial.sendTo.map(function(sendTo) {
|
|
913
933
|
return sendTo.email;
|
|
914
934
|
});
|
|
915
935
|
|
|
@@ -942,10 +962,11 @@ module.exports = function(mongoose, config) {
|
|
|
942
962
|
var query = this.find({
|
|
943
963
|
'snapshots': {
|
|
944
964
|
$elemMatch: {
|
|
965
|
+
'form.0': { $exists: true },
|
|
945
966
|
'notifications.company.reminders.sendTo': { $gt: [] },
|
|
946
967
|
'notifications.company.reminders.sendOn': { $lte: now },
|
|
947
968
|
'notifications.company.reminders.sentOn': null,
|
|
948
|
-
'status': { $nin: ['Pending', 'Completed', 'Archived'] }
|
|
969
|
+
'status': { $nin: ['Pending', 'Completed', 'Archived', 'Skipped'] }
|
|
949
970
|
}
|
|
950
971
|
}
|
|
951
972
|
}).select({
|
|
@@ -955,6 +976,8 @@ module.exports = function(mongoose, config) {
|
|
|
955
976
|
'snapshots.uuid': 1,
|
|
956
977
|
'snapshots.year': 1,
|
|
957
978
|
'snapshots.period': 1,
|
|
979
|
+
'snapshots.form._id': 1,
|
|
980
|
+
'snapshots.status': 1,
|
|
958
981
|
'snapshots.notifications.company.reminders.sendTo': 1,
|
|
959
982
|
'snapshots.notifications.company.reminders.sendOn': 1,
|
|
960
983
|
'snapshots.notifications.company.reminders.sentOn': 1,
|
|
@@ -967,7 +990,12 @@ module.exports = function(mongoose, config) {
|
|
|
967
990
|
|
|
968
991
|
_.each(financial.snapshots, function(snapshot) {
|
|
969
992
|
|
|
970
|
-
|
|
993
|
+
if (!snapshot.form || snapshot.form.length === 0) return;
|
|
994
|
+
if (_.contains(['Pending', 'Completed', 'Archived', 'Skipped'], snapshot.status)) return;
|
|
995
|
+
var reminders = snapshot.notifications && snapshot.notifications.company && snapshot.notifications.company.reminders;
|
|
996
|
+
if (!Array.isArray(reminders)) return;
|
|
997
|
+
|
|
998
|
+
_.each(reminders, function(reminder) {
|
|
971
999
|
|
|
972
1000
|
var hasSendTo = reminder.sendTo && reminder.sendTo.length > 0;
|
|
973
1001
|
var hasSendOnLessThanNow = reminder.sendOn && reminder.sendOn < now;
|
|
@@ -1012,10 +1040,11 @@ module.exports = function(mongoose, config) {
|
|
|
1012
1040
|
var query = this.find({
|
|
1013
1041
|
'snapshots': {
|
|
1014
1042
|
$elemMatch: {
|
|
1043
|
+
'form.0': { $exists: true },
|
|
1015
1044
|
'notifications.company.rejections.sendTo': { $gt: [] },
|
|
1016
1045
|
'notifications.company.rejections.sendOn': { $lte: now },
|
|
1017
1046
|
'notifications.company.rejections.sentOn': null,
|
|
1018
|
-
'status': { $nin: ['Pending', 'Completed', 'Archived'] }
|
|
1047
|
+
'status': { $nin: ['Pending', 'Completed', 'Archived', 'Skipped'] }
|
|
1019
1048
|
}
|
|
1020
1049
|
}
|
|
1021
1050
|
}).select({
|
|
@@ -1025,6 +1054,8 @@ module.exports = function(mongoose, config) {
|
|
|
1025
1054
|
'snapshots.uuid': 1,
|
|
1026
1055
|
'snapshots.year': 1,
|
|
1027
1056
|
'snapshots.period': 1,
|
|
1057
|
+
'snapshots.form._id': 1,
|
|
1058
|
+
'snapshots.status': 1,
|
|
1028
1059
|
'snapshots.notifications.company.rejections.sendTo': 1,
|
|
1029
1060
|
'snapshots.notifications.company.rejections.sendOn': 1,
|
|
1030
1061
|
'snapshots.notifications.company.rejections.sentOn': 1,
|
|
@@ -1037,7 +1068,12 @@ module.exports = function(mongoose, config) {
|
|
|
1037
1068
|
|
|
1038
1069
|
_.each(financial.snapshots, function(snapshot) {
|
|
1039
1070
|
|
|
1040
|
-
|
|
1071
|
+
if (!snapshot.form || snapshot.form.length === 0) return;
|
|
1072
|
+
if (_.contains(['Pending', 'Completed', 'Archived', 'Skipped'], snapshot.status)) return;
|
|
1073
|
+
var rejections = snapshot.notifications && snapshot.notifications.company && snapshot.notifications.company.rejections;
|
|
1074
|
+
if (!Array.isArray(rejections)) return;
|
|
1075
|
+
|
|
1076
|
+
_.each(rejections, function(rejection) {
|
|
1041
1077
|
|
|
1042
1078
|
var hasSendTo = rejection.sendTo && rejection.sendTo.length > 0;
|
|
1043
1079
|
var hasSendOnLessThanNow = rejection.sendOn && rejection.sendOn < now;
|
|
@@ -1255,7 +1291,9 @@ module.exports = function(mongoose, config) {
|
|
|
1255
1291
|
const query = this.find({
|
|
1256
1292
|
'snapshots': {
|
|
1257
1293
|
$elemMatch: {
|
|
1258
|
-
'
|
|
1294
|
+
'form.0': { $exists: true },
|
|
1295
|
+
'submittedOn': null,
|
|
1296
|
+
'status': { $nin: ['Pending', 'Completed', 'Archived', 'Overdue', 'Skipped'] },
|
|
1259
1297
|
'dueOn': { $lte: new Date() }
|
|
1260
1298
|
}
|
|
1261
1299
|
}
|
|
@@ -1339,27 +1377,40 @@ module.exports = function(mongoose, config) {
|
|
|
1339
1377
|
var isApproved = snapshot.approvedOn;
|
|
1340
1378
|
var isRejected = snapshot.rejectedOn && snapshot.rejection;
|
|
1341
1379
|
var isArchived = snapshot.archivedOn;
|
|
1342
|
-
var
|
|
1380
|
+
var hasForm = snapshot.form && snapshot.form.length > 0;
|
|
1381
|
+
var isOverdue = hasForm && !isSubmitted && snapshot.dueOn
|
|
1382
|
+
? moment(new Date()).isSameOrAfter(moment(snapshot.dueOn))
|
|
1383
|
+
: false;
|
|
1343
1384
|
|
|
1344
1385
|
var isOpened = false;
|
|
1345
1386
|
var isDelivered = false;
|
|
1346
1387
|
var isScheduled = false;
|
|
1388
|
+
var hasDeliveryAttempt = false;
|
|
1347
1389
|
if(snapshot.notifications && snapshot.notifications.company && snapshot.notifications.company.initial) {
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1390
|
+
var initial = snapshot.notifications.company.initial;
|
|
1391
|
+
isOpened = hasForm && (initial.status === 'Opened' || initial.status === 'Clicked');
|
|
1392
|
+
isDelivered = hasForm && initial.sentOn;
|
|
1393
|
+
isScheduled = hasForm && initial.sendOn;
|
|
1394
|
+
hasDeliveryAttempt = initial.sentOn || initial.postmarkMessageId || initial.status;
|
|
1351
1395
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1396
|
+
var isSkipped = snapshot.status === 'Skipped' && hasForm && isScheduled &&
|
|
1397
|
+
!hasDeliveryAttempt && !isSubmitted;
|
|
1398
|
+
|
|
1399
|
+
// Skipped represents a scheduled request that was unnecessary before
|
|
1400
|
+
// delivery. If someone later explicitly submits that visible form, the
|
|
1401
|
+
// normal submission/approval lifecycle must take precedence.
|
|
1402
|
+
if(isArchived) snapshot.status = 'Archived';
|
|
1403
|
+
else if(isSkipped) snapshot.status = 'Skipped';
|
|
1404
|
+
else if(snapshot.submittedBy === 'Totem Sheet') snapshot.status = 'Completed'; // sheet snapshots should always be completed
|
|
1354
1405
|
else if(needsApproval && isApproved) snapshot.status = 'Completed';
|
|
1355
1406
|
else if(!needsApproval && isSubmitted) snapshot.status = 'Completed';
|
|
1356
|
-
else if(isArchived) snapshot.status = 'Archived';
|
|
1357
1407
|
else if(needsApproval && isSubmitted && !isRejected) snapshot.status = 'Pending';
|
|
1358
1408
|
else if(needsApproval && isSubmitted && isRejected) snapshot.status = 'Opened';
|
|
1359
1409
|
else if(isOverdue) snapshot.status = 'Overdue';
|
|
1360
1410
|
else if(isDelivered && isOpened) snapshot.status = 'Opened';
|
|
1361
1411
|
else if(isDelivered) snapshot.status = 'Delivered';
|
|
1362
1412
|
else if(isScheduled) snapshot.status = 'Scheduled'; // when a snapshot is scheduled, but not yet sent (checks every 5 minutes)
|
|
1413
|
+
else snapshot.status = undefined;
|
|
1363
1414
|
|
|
1364
1415
|
});
|
|
1365
1416
|
|
package/package.json
CHANGED
package/test/Financials.js
CHANGED
|
@@ -40,6 +40,29 @@ describe('Financials', function() {
|
|
|
40
40
|
snapshot.metrics[0].scale.should.equal(1000);
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
+
it('preserves narrative text evidence on current and previous metric sources', function() {
|
|
44
|
+
var position = { text: {
|
|
45
|
+
quote: 'Revenue was $1.2m in July.', prefix: 'Intro\n', suffix: '\nThanks'
|
|
46
|
+
} };
|
|
47
|
+
var source = { model: 'Message', ref: new mongoose.Types.ObjectId(), position: position };
|
|
48
|
+
var f = new Financials({
|
|
49
|
+
organization: orgA,
|
|
50
|
+
customer: customerA,
|
|
51
|
+
snapshots: [{
|
|
52
|
+
uuid: 'narrative-source-evidence', year: 2026, period: 'FY',
|
|
53
|
+
metrics: [{
|
|
54
|
+
name: 'Revenue', scenario: 'actual', value: 1200000,
|
|
55
|
+
sources: [source],
|
|
56
|
+
previous: { value: 1000000, sources: [source] }
|
|
57
|
+
}]
|
|
58
|
+
}]
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
var metric = f.snapshots[0].metrics[0].toObject();
|
|
62
|
+
metric.sources[0].position.should.deepEqual(position);
|
|
63
|
+
metric.previous.sources[0].position.should.deepEqual(position);
|
|
64
|
+
});
|
|
65
|
+
|
|
43
66
|
before(function(done) {
|
|
44
67
|
databaseHelpers.connect(mongoose, done);
|
|
45
68
|
});
|
|
@@ -163,6 +186,43 @@ describe('Financials', function() {
|
|
|
163
186
|
|
|
164
187
|
});
|
|
165
188
|
|
|
189
|
+
it('preserves Skipped and excludes skipped snapshots from sends and overdue work', async function() {
|
|
190
|
+
var uuid = 'complete-request-skipped';
|
|
191
|
+
var f = await Financials.create({
|
|
192
|
+
organization: new mongoose.Types.ObjectId(),
|
|
193
|
+
customer: new mongoose.Types.ObjectId(),
|
|
194
|
+
snapshots: [{
|
|
195
|
+
uuid: uuid,
|
|
196
|
+
year: 2026,
|
|
197
|
+
period: 'Q1',
|
|
198
|
+
status: 'Skipped',
|
|
199
|
+
dueOn: new Date(Date.now() - 86400000),
|
|
200
|
+
notifications: {
|
|
201
|
+
company: {
|
|
202
|
+
initial: {
|
|
203
|
+
sendOn: new Date(Date.now() - 3600000),
|
|
204
|
+
sendTo: [{ email: 'company@example.com' }]
|
|
205
|
+
},
|
|
206
|
+
reminders: [{
|
|
207
|
+
sendOn: new Date(Date.now() - 1800000),
|
|
208
|
+
sendTo: [{ email: 'company@example.com' }]
|
|
209
|
+
}]
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}]
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
var reloaded = await Financials.findById(f._id);
|
|
216
|
+
reloaded.snapshots[0].status.should.equal('Skipped');
|
|
217
|
+
|
|
218
|
+
var initial = await Financials.getCompanyInitialToSend();
|
|
219
|
+
initial.some(function(item) { return item.uuid === uuid; }).should.equal(false);
|
|
220
|
+
var reminders = await Financials.getCompanyReminderToSend();
|
|
221
|
+
reminders.some(function(item) { return item.uuid === uuid; }).should.equal(false);
|
|
222
|
+
var overdue = await Financials.getOverdue();
|
|
223
|
+
overdue.some(function(item) { return item._id.toString() === f._id.toString(); }).should.equal(false);
|
|
224
|
+
});
|
|
225
|
+
|
|
166
226
|
describe.skip('old so skip for now', function() {
|
|
167
227
|
|
|
168
228
|
it('gets financials for an org', function(done) {
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var should = require('should');
|
|
4
|
+
var mongoose = require('mongoose');
|
|
5
|
+
var config = require('./config')['test'];
|
|
6
|
+
|
|
7
|
+
if (!mongoose.models.Financials) { require('../index')(mongoose, config); }
|
|
8
|
+
var Financials = mongoose.model('Financials');
|
|
9
|
+
|
|
10
|
+
describe('Financials snapshot status policy (no database)', function() {
|
|
11
|
+
it('derives every snapshot status from one consistent precedence matrix', function() {
|
|
12
|
+
var now = Date.now();
|
|
13
|
+
var snapshots = [
|
|
14
|
+
{
|
|
15
|
+
uuid: 'scheduled', year: 2026, period: 'January',
|
|
16
|
+
notifications: { company: { initial: { sendOn: new Date(now + 60000) } } }
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
uuid: 'delivered', year: 2026, period: 'February',
|
|
20
|
+
notifications: { company: { initial: { sendOn: new Date(now - 60000), sentOn: new Date(now - 60000), status: 'Sent' } } }
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
uuid: 'opened', year: 2026, period: 'March',
|
|
24
|
+
notifications: { company: { initial: { sendOn: new Date(now - 60000), sentOn: new Date(now - 60000), status: 'Opened' } } }
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
uuid: 'overdue', year: 2026, period: 'April', dueOn: new Date(now - 60000),
|
|
28
|
+
notifications: { company: { initial: { sendOn: new Date(now - 120000), sentOn: new Date(now - 120000) } } }
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
uuid: 'skipped', year: 2026, period: 'May', status: 'Skipped',
|
|
32
|
+
form: [{ name: 'Revenue', type: 'metric' }],
|
|
33
|
+
notifications: { company: { initial: { sendOn: new Date(now + 60000) } } }
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
uuid: 'pending', year: 2026, period: 'June', approval: true,
|
|
37
|
+
submittedOn: new Date(now - 60000), submittedBy: 'company@example.com'
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
uuid: 'completed', year: 2026, period: 'July', approval: false,
|
|
41
|
+
submittedOn: new Date(now - 60000), submittedBy: 'investor@example.com'
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
uuid: 'approved', year: 2026, period: 'August', approval: true,
|
|
45
|
+
submittedOn: new Date(now - 120000), approvedOn: new Date(now - 60000)
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
uuid: 'rejected', year: 2026, period: 'September', approval: true,
|
|
49
|
+
submittedOn: new Date(now - 120000), rejectedOn: new Date(now - 60000), rejection: 'Please revise'
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
uuid: 'archived', year: 2026, period: 'October', status: 'Completed',
|
|
53
|
+
submittedOn: new Date(now - 120000), archivedOn: new Date(now - 60000)
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
uuid: 'sheet', year: 2026, period: 'November', submittedBy: 'Totem Sheet'
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
snapshots.forEach(function(snapshot) {
|
|
60
|
+
if (snapshot.uuid !== 'sheet') snapshot.form = [{ name: 'Revenue', type: 'metric' }];
|
|
61
|
+
});
|
|
62
|
+
var financials = Financials.hydrate({
|
|
63
|
+
_id: new mongoose.Types.ObjectId(),
|
|
64
|
+
customer: new mongoose.Types.ObjectId(),
|
|
65
|
+
organization: new mongoose.Types.ObjectId(),
|
|
66
|
+
snapshots: snapshots
|
|
67
|
+
});
|
|
68
|
+
var expected = {
|
|
69
|
+
scheduled: 'Scheduled',
|
|
70
|
+
delivered: 'Delivered',
|
|
71
|
+
opened: 'Opened',
|
|
72
|
+
overdue: 'Overdue',
|
|
73
|
+
skipped: 'Skipped',
|
|
74
|
+
pending: 'Pending',
|
|
75
|
+
completed: 'Completed',
|
|
76
|
+
approved: 'Completed',
|
|
77
|
+
rejected: 'Opened',
|
|
78
|
+
archived: 'Archived',
|
|
79
|
+
sheet: 'Completed'
|
|
80
|
+
};
|
|
81
|
+
financials.snapshots.forEach(function(snapshot) {
|
|
82
|
+
snapshot.status.should.equal(expected[snapshot.uuid]);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('preserves Skipped when a scheduled snapshot is hydrated', function() {
|
|
87
|
+
var financials = Financials.hydrate({
|
|
88
|
+
_id: new mongoose.Types.ObjectId(),
|
|
89
|
+
customer: new mongoose.Types.ObjectId(),
|
|
90
|
+
organization: new mongoose.Types.ObjectId(),
|
|
91
|
+
snapshots: [{
|
|
92
|
+
uuid: 'skipped-hydration',
|
|
93
|
+
year: 2026,
|
|
94
|
+
period: 'Q1',
|
|
95
|
+
status: 'Skipped',
|
|
96
|
+
form: [{ name: 'Revenue', type: 'metric' }],
|
|
97
|
+
notifications: { company: { initial: { sendOn: new Date() } } }
|
|
98
|
+
}]
|
|
99
|
+
});
|
|
100
|
+
financials.snapshots[0].status.should.equal('Skipped');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('lets an explicit submission supersede Skipped', function() {
|
|
104
|
+
var financials = Financials.hydrate({
|
|
105
|
+
_id: new mongoose.Types.ObjectId(),
|
|
106
|
+
customer: new mongoose.Types.ObjectId(),
|
|
107
|
+
organization: new mongoose.Types.ObjectId(),
|
|
108
|
+
snapshots: [{
|
|
109
|
+
uuid: 'submitted-after-skip',
|
|
110
|
+
year: 2026,
|
|
111
|
+
period: 'Q1',
|
|
112
|
+
status: 'Skipped',
|
|
113
|
+
form: [{ name: 'Revenue', type: 'metric' }],
|
|
114
|
+
submittedOn: new Date(),
|
|
115
|
+
submittedBy: 'investor@example.com',
|
|
116
|
+
approval: false,
|
|
117
|
+
notifications: { company: { initial: { sendOn: new Date() } } }
|
|
118
|
+
}]
|
|
119
|
+
});
|
|
120
|
+
financials.snapshots[0].status.should.equal('Completed');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('gives Archived precedence over completed and skipped states', function() {
|
|
124
|
+
var financials = Financials.hydrate({
|
|
125
|
+
_id: new mongoose.Types.ObjectId(),
|
|
126
|
+
customer: new mongoose.Types.ObjectId(),
|
|
127
|
+
organization: new mongoose.Types.ObjectId(),
|
|
128
|
+
snapshots: [{
|
|
129
|
+
uuid: 'archived-complete',
|
|
130
|
+
year: 2026,
|
|
131
|
+
period: 'Q1',
|
|
132
|
+
status: 'Completed',
|
|
133
|
+
submittedOn: new Date(),
|
|
134
|
+
archivedOn: new Date()
|
|
135
|
+
}, {
|
|
136
|
+
uuid: 'archived-skipped',
|
|
137
|
+
year: 2026,
|
|
138
|
+
period: 'Q2',
|
|
139
|
+
status: 'Skipped',
|
|
140
|
+
form: [{ name: 'Revenue', type: 'metric' }],
|
|
141
|
+
archivedOn: new Date(),
|
|
142
|
+
notifications: { company: { initial: { sendOn: new Date() } } }
|
|
143
|
+
}]
|
|
144
|
+
});
|
|
145
|
+
financials.snapshots[0].status.should.equal('Archived');
|
|
146
|
+
financials.snapshots[1].status.should.equal('Archived');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('does not preserve Skipped outside an unsent scheduled form', function() {
|
|
150
|
+
var financials = Financials.hydrate({
|
|
151
|
+
_id: new mongoose.Types.ObjectId(),
|
|
152
|
+
customer: new mongoose.Types.ObjectId(),
|
|
153
|
+
organization: new mongoose.Types.ObjectId(),
|
|
154
|
+
snapshots: [{
|
|
155
|
+
uuid: 'data-only-skipped', year: 2026, period: 'Q1', status: 'Skipped', form: []
|
|
156
|
+
}, {
|
|
157
|
+
uuid: 'data-only-scheduled', year: 2026, period: 'Q2', status: 'Scheduled', form: [],
|
|
158
|
+
notifications: { company: { initial: { sendOn: new Date() } } }
|
|
159
|
+
}, {
|
|
160
|
+
uuid: 'sent-skipped', year: 2026, period: 'Q3', status: 'Skipped',
|
|
161
|
+
form: [{ name: 'Revenue', type: 'metric' }],
|
|
162
|
+
notifications: { company: { initial: {
|
|
163
|
+
sendOn: new Date(), sentOn: new Date(), status: 'Opened'
|
|
164
|
+
} } }
|
|
165
|
+
}]
|
|
166
|
+
});
|
|
167
|
+
should(financials.snapshots[0].status).equal(undefined);
|
|
168
|
+
should(financials.snapshots[1].status).equal(undefined);
|
|
169
|
+
financials.snapshots[2].status.should.equal('Opened');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('only considers form-bearing, unsubmitted requests eligible for Overdue', async function() {
|
|
173
|
+
var capturedFilter;
|
|
174
|
+
var originalFind = Financials.find;
|
|
175
|
+
Financials.find = function(filter) {
|
|
176
|
+
capturedFilter = filter;
|
|
177
|
+
return Promise.resolve([]);
|
|
178
|
+
};
|
|
179
|
+
try {
|
|
180
|
+
await Financials.getOverdue();
|
|
181
|
+
var eligibility = capturedFilter.snapshots.$elemMatch;
|
|
182
|
+
eligibility['form.0'].should.deepEqual({ $exists: true });
|
|
183
|
+
should(eligibility.submittedOn).eql(null);
|
|
184
|
+
eligibility.status.$nin.should.containEql('Pending');
|
|
185
|
+
} finally {
|
|
186
|
+
Financials.find = originalFind;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('excludes Skipped from the company initial-send query', async function() {
|
|
191
|
+
var capturedFilter;
|
|
192
|
+
var originalFind = Financials.find;
|
|
193
|
+
Financials.find = function(filter) {
|
|
194
|
+
capturedFilter = filter;
|
|
195
|
+
return {
|
|
196
|
+
select: function() {
|
|
197
|
+
return { lean: async function() { return []; } };
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
try {
|
|
202
|
+
var results = await Financials.getCompanyInitialToSend();
|
|
203
|
+
results.should.deepEqual([]);
|
|
204
|
+
capturedFilter.snapshots.$elemMatch.status.$nin.should.containEql('Skipped');
|
|
205
|
+
capturedFilter.snapshots.$elemMatch['form.0'].should.deepEqual({ $exists: true });
|
|
206
|
+
} finally {
|
|
207
|
+
Financials.find = originalFind;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('requires a form in every company notification query', async function() {
|
|
212
|
+
var capturedFilters = [];
|
|
213
|
+
var originalFind = Financials.find;
|
|
214
|
+
Financials.find = function(filter) {
|
|
215
|
+
capturedFilters.push(filter);
|
|
216
|
+
return {
|
|
217
|
+
select: function() {
|
|
218
|
+
return { lean: async function() { return []; } };
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
try {
|
|
223
|
+
await Financials.getCompanyInitialToSend();
|
|
224
|
+
await Financials.getCompanyReminderToSend();
|
|
225
|
+
await Financials.getCompanyRejectionToSend();
|
|
226
|
+
capturedFilters.length.should.equal(3);
|
|
227
|
+
capturedFilters.forEach(function(filter) {
|
|
228
|
+
filter.snapshots.$elemMatch['form.0'].should.deepEqual({ $exists: true });
|
|
229
|
+
});
|
|
230
|
+
} finally {
|
|
231
|
+
Financials.find = originalFind;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
});
|