@tamu-dor/famis-db 1.3.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/change.txt +123 -0
- package/changes.md +405 -0
- package/docs/assets/anchor.js +350 -0
- package/docs/assets/bass-addons.css +12 -0
- package/docs/assets/bass.css +544 -0
- package/docs/assets/fonts/EOT/SourceCodePro-Bold.eot +0 -0
- package/docs/assets/fonts/EOT/SourceCodePro-Regular.eot +0 -0
- package/docs/assets/fonts/LICENSE.txt +93 -0
- package/docs/assets/fonts/OTF/SourceCodePro-Bold.otf +0 -0
- package/docs/assets/fonts/OTF/SourceCodePro-Regular.otf +0 -0
- package/docs/assets/fonts/TTF/SourceCodePro-Bold.ttf +0 -0
- package/docs/assets/fonts/TTF/SourceCodePro-Regular.ttf +0 -0
- package/docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff +0 -0
- package/docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff +0 -0
- package/docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff +0 -0
- package/docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff +0 -0
- package/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2 +0 -0
- package/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2 +0 -0
- package/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2 +0 -0
- package/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2 +0 -0
- package/docs/assets/fonts/source-code-pro.css +23 -0
- package/docs/assets/github.css +123 -0
- package/docs/assets/site.js +168 -0
- package/docs/assets/split.css +15 -0
- package/docs/assets/split.js +782 -0
- package/docs/assets/style.css +147 -0
- package/docs/index.html +3737 -0
- package/docs.md +591 -0
- package/famisDb.pdf +0 -0
- package/index.js +1497 -0
- package/package.json +25 -0
- package/readme.md +38 -0
- package/readme.wiki +332 -0
- package/schemas/00-initial-data.sql +116 -0
- package/schemas/migrations/201906101221.sql +29 -0
- package/schemas/migrations/201907081530.sql +51 -0
- package/schemas/migrations/201908230849.sql +40 -0
- package/schemas/migrations/201910031358.sql +73 -0
- package/schemas/migrations/20191024.sql +38 -0
- package/schemas/migrations/201911060915.sql +3 -0
- package/schemas/migrations/202001221335.sql +58 -0
- package/schemas/migrations/202003041114.sql +53 -0
- package/schemas/migrations/202003261143.sql +47 -0
- package/schemas/migrations/202004201456.sql +3 -0
- package/schemas/migrations/20200722.sql +92 -0
- package/schemas/migrations/202007281124.sql +17 -0
- package/schemas/migrations/20200729.sql +76 -0
- package/schemas/migrations/20200806-FCF.sql +72 -0
- package/schemas/migrations/20200806-IMAC.sql +60 -0
- package/schemas/migrations/20200811.sql +46 -0
- package/schemas/migrations/20200813.sql +36 -0
- package/schemas/migrations/202010210924.sql +66 -0
- package/schemas/migrations/202010211558.sql +20 -0
- package/schemas/migrations/202011130651.sql +4 -0
- package/schemas/migrations/202011130951.sql +17 -0
- package/schemas/migrations/202101201051.sql +33 -0
- package/schemas/migrations/postponed-202004221221.sql +66 -0
- package/schemas/schema.sql +155 -0
package/index.js
ADDED
|
@@ -0,0 +1,1497 @@
|
|
|
1
|
+
const knex = require('knex');
|
|
2
|
+
const cuid = require('cuid');
|
|
3
|
+
const { map } = require('awaity');
|
|
4
|
+
const revisionHash = require('rev-hash');
|
|
5
|
+
|
|
6
|
+
const famisInvoiceNumbers = [
|
|
7
|
+
{
|
|
8
|
+
start: 300128,
|
|
9
|
+
end: 399999,
|
|
10
|
+
prefix: 'I',
|
|
11
|
+
aliases: [
|
|
12
|
+
'ilabs',
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
start: 100000,
|
|
17
|
+
end: 999999,
|
|
18
|
+
prefix: 'V',
|
|
19
|
+
aliases: [
|
|
20
|
+
'mosaic',
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
const returningColumns = ['uid', 'number', 'lab', 'totalAmount', 'data', 'date', 'type', 'famisInvoiceNumber', 'customerAccount', 'sellingAccount'];
|
|
25
|
+
|
|
26
|
+
const pad = (str, max, right) => {
|
|
27
|
+
let retVal = str;
|
|
28
|
+
str = str.toString();
|
|
29
|
+
if (str.length < max) {
|
|
30
|
+
retVal = pad("0" + str, max);
|
|
31
|
+
} else if (str.length > max) {
|
|
32
|
+
retVal = str.substring(0, max);
|
|
33
|
+
}
|
|
34
|
+
if (right) {
|
|
35
|
+
retVal = retVal.length < max ? pad(retVal + "0", max, right) : retVal;
|
|
36
|
+
}
|
|
37
|
+
return retVal;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const sanitizeAccountNumber = (account) => pad(account.replace(/\D/g,''), 13, true);
|
|
41
|
+
|
|
42
|
+
const getCoreAccountNumber = (famis) => {
|
|
43
|
+
const { sellingCampusCode, sellingAccountNumber, sellingSupportAccount } = famis;
|
|
44
|
+
const account = `${sellingCampusCode}${sellingAccountNumber}${sellingSupportAccount}`;
|
|
45
|
+
return sanitizeAccountNumber(account);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const sortObjectKeys = (obj) => {
|
|
49
|
+
if (obj === null || obj === undefined) {
|
|
50
|
+
return obj;
|
|
51
|
+
}
|
|
52
|
+
if (typeof obj !== 'object') {
|
|
53
|
+
// it is a primitive: number/string (in an array)
|
|
54
|
+
return obj;
|
|
55
|
+
}
|
|
56
|
+
const result = Object.keys(obj).sort().reduce(
|
|
57
|
+
(acc,key) => {
|
|
58
|
+
if (Array.isArray(obj[key])) {
|
|
59
|
+
acc[key] = obj[key].map(sortObjectKeys);
|
|
60
|
+
} else if (typeof obj[key] === 'object') {
|
|
61
|
+
acc[key] = sortObjectKeys(obj[key]);
|
|
62
|
+
} else {
|
|
63
|
+
acc[key] = obj[key];
|
|
64
|
+
}
|
|
65
|
+
return acc;
|
|
66
|
+
},
|
|
67
|
+
{},
|
|
68
|
+
);
|
|
69
|
+
return result;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
class FAMISDb {
|
|
73
|
+
constructor(config, people) {
|
|
74
|
+
if (config) {
|
|
75
|
+
this.init(config, people);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
init(config, people) {
|
|
80
|
+
this.db = knex({
|
|
81
|
+
client: 'pg',
|
|
82
|
+
connection: config,
|
|
83
|
+
});
|
|
84
|
+
this.people = people;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
*
|
|
89
|
+
* Extract customer account and po number
|
|
90
|
+
* @param {object} famis
|
|
91
|
+
* @param {object} replace_data
|
|
92
|
+
* @returns {object} account number object
|
|
93
|
+
* @memberof FAMISDb
|
|
94
|
+
*/
|
|
95
|
+
getAccountNumber(famis, replace_data) {
|
|
96
|
+
const retVal = {
|
|
97
|
+
accountNumber: null,
|
|
98
|
+
poNumber: null,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (famis.customerAccountNumber || (replace_data && replace_data.customerAccountNumber)) {
|
|
102
|
+
retVal.accountNumber = replace_data && replace_data.customerAccountNumber ? replace_data.customerAccountNumber : famis.customerAccountNumber;
|
|
103
|
+
} else if (famis.customerPONumber.includes('|') || (replace_data && replace_data.customerPONumber.includes('|'))) {
|
|
104
|
+
const result = replace_data && replace_data.customerPONumber.includes('|') ? replace_data.customerPONumber.split('|') : famis.customerPONumber.split('|');
|
|
105
|
+
retVal.accountNumber = sanitizeAccountNumber(result[0]);
|
|
106
|
+
retVal.poNumber = result[1].trim();
|
|
107
|
+
} else {
|
|
108
|
+
retVal.accountNumber = replace_data && replace_data.customerPONumber ? sanitizeAccountNumber(replace_data.customerPONumber) : sanitizeAccountNumber(famis.customerPONumber);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return retVal;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
*
|
|
116
|
+
* Process an invoice to have all necessary relationships
|
|
117
|
+
* @param {object} invoice
|
|
118
|
+
* @returns{Promise<object>} invoice object
|
|
119
|
+
* @memberof FAMISDb
|
|
120
|
+
*/
|
|
121
|
+
async processInvoice(invoice) {
|
|
122
|
+
// console.log(invoice);
|
|
123
|
+
const account = getCoreAccountNumber(invoice.data.details[0].famis);
|
|
124
|
+
invoice.core = await this.getCoreByAccount(account);
|
|
125
|
+
if (invoice.core) {
|
|
126
|
+
invoice.core.systemPart = await this.getSystemById(invoice.core.systemId);
|
|
127
|
+
} else {
|
|
128
|
+
const e = Error(`Revenue account ${account} is not associated with a core.`)
|
|
129
|
+
throw(e);
|
|
130
|
+
}
|
|
131
|
+
const customerAccountNumber = this.getAccountNumber(invoice.data.details[0].famis, invoice.replace_data);
|
|
132
|
+
invoice.isIdt = customerAccountNumber && (customerAccountNumber.accountNumber.substring(0,2) === account.substring(0,2));
|
|
133
|
+
invoice.customerAccountInfo = await this.getByAccountNumber(customerAccountNumber.accountNumber);
|
|
134
|
+
invoice.poNumber = customerAccountNumber.poNumber;
|
|
135
|
+
invoice.customerAccount = customerAccountNumber.accountNumber;
|
|
136
|
+
if (invoice.customerAccountInfo && this.people) {
|
|
137
|
+
const pi = invoice.customerAccountInfo.uin ? await this.people.getByUin(invoice.customerAccountInfo.uin) : [];
|
|
138
|
+
if (pi.length) {
|
|
139
|
+
invoice.pi = JSON.parse(JSON.stringify(pi[0]));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return invoice;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
*
|
|
148
|
+
* Get invoice by uid
|
|
149
|
+
* @param {string} uid
|
|
150
|
+
* @returns{Promise<object>} invoice object
|
|
151
|
+
* @memberof FAMISDb
|
|
152
|
+
*/
|
|
153
|
+
async getInvoiceByUid(uid) {
|
|
154
|
+
let records;
|
|
155
|
+
let retVal;
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
records = await this.db('invoices').where('uid', uid);
|
|
159
|
+
if (records.length) {
|
|
160
|
+
retVal = this.processInvoice(records[0]);
|
|
161
|
+
}
|
|
162
|
+
} catch (e) {
|
|
163
|
+
throw e;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return retVal;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
*
|
|
171
|
+
* Get invoice by originating system assigned number
|
|
172
|
+
* @param {string} number
|
|
173
|
+
* @returns{Promise<object>} invoice object
|
|
174
|
+
* @memberof FAMISDb
|
|
175
|
+
*/
|
|
176
|
+
async getInvoiceByNumber(number) {
|
|
177
|
+
let records;
|
|
178
|
+
let retVal;
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
records = await this.db('invoices').where('number', number);
|
|
182
|
+
if (records.length) {
|
|
183
|
+
retVal = this.processInvoice(records[0]);
|
|
184
|
+
}
|
|
185
|
+
} catch (e) {
|
|
186
|
+
throw e;
|
|
187
|
+
}
|
|
188
|
+
return retVal;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
*
|
|
193
|
+
* Get invoice by fingerprint
|
|
194
|
+
* @param {string} fingerPrint
|
|
195
|
+
* @returns{Promise<object>} invoice object
|
|
196
|
+
* @memberof FAMISDb
|
|
197
|
+
*/
|
|
198
|
+
async getInvoiceByFingerPrint(fingerPrint) {
|
|
199
|
+
let records;
|
|
200
|
+
let retVal;
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
records = await this.db('invoices').where('fingerprint', fingerPrint);
|
|
204
|
+
if (records.length) {
|
|
205
|
+
retVal = this.processInvoice(records[0]);
|
|
206
|
+
}
|
|
207
|
+
} catch (e) {
|
|
208
|
+
throw e;
|
|
209
|
+
}
|
|
210
|
+
return retVal;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
*
|
|
215
|
+
* Get all invoices associated with a specified account number
|
|
216
|
+
* @param {string} accountNumber
|
|
217
|
+
* @returns{Promise<array>} invoice objects
|
|
218
|
+
* @memberof FAMISDb
|
|
219
|
+
*/
|
|
220
|
+
async getInvoicesByAccount(accountNumber) {
|
|
221
|
+
const self = this;
|
|
222
|
+
let records;
|
|
223
|
+
let retVal;
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
let records = await this.db('invoices')
|
|
227
|
+
.whereNull('deleted')
|
|
228
|
+
.andWhere(function() {
|
|
229
|
+
this.where('customerAccount', accountNumber);
|
|
230
|
+
this.orWhere('sellingAccount', accountNumber);
|
|
231
|
+
this.orWhereRaw("replace_data->>'customerAccountNumber' = ?",[accountNumber]);
|
|
232
|
+
this.orWhereRaw("replace_data->>'customerPONumber' LIKE ?",[`${accountNumber}%`]);
|
|
233
|
+
});
|
|
234
|
+
if (records.length) {
|
|
235
|
+
records = await map(
|
|
236
|
+
records,
|
|
237
|
+
async (record) => {
|
|
238
|
+
const rec = self.processInvoice(record);
|
|
239
|
+
return rec;
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
retVal = records;
|
|
244
|
+
} catch (e) {
|
|
245
|
+
throw e;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return retVal;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
*
|
|
253
|
+
* Get all invoice ordered by and sorted by paramaters passed
|
|
254
|
+
* @param {string} [orderBy='date']
|
|
255
|
+
* @param {string} [dir='desc']
|
|
256
|
+
* @param {boolean} [deleted=false]
|
|
257
|
+
* @returns{Promise<array>} invoice objects
|
|
258
|
+
* @memberof FAMISDb
|
|
259
|
+
*/
|
|
260
|
+
async getAllInvoices(orderBy='date', dir='desc', deleted=false) {
|
|
261
|
+
const self = this;
|
|
262
|
+
let records;
|
|
263
|
+
try {
|
|
264
|
+
const sql = this.db('invoices')
|
|
265
|
+
if (!deleted) {
|
|
266
|
+
sql.whereNull('deleted')
|
|
267
|
+
}
|
|
268
|
+
records = await sql.orderBy(orderBy, dir);
|
|
269
|
+
records = await map(
|
|
270
|
+
records,
|
|
271
|
+
async (record) => {
|
|
272
|
+
const rec = self.processInvoice(record);
|
|
273
|
+
return rec;
|
|
274
|
+
}
|
|
275
|
+
);
|
|
276
|
+
} catch (e) {
|
|
277
|
+
throw e;
|
|
278
|
+
}
|
|
279
|
+
return records;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
*
|
|
284
|
+
* Retreives invoices from the latest date
|
|
285
|
+
* @returns{Promise<array>} invoice objects
|
|
286
|
+
* @memberof FAMISDb
|
|
287
|
+
*/
|
|
288
|
+
async getLatestInvoices() {
|
|
289
|
+
const self = this;
|
|
290
|
+
let records;
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
records = await this.db('invoices')
|
|
294
|
+
.whereNull('deleted')
|
|
295
|
+
.whereIn('date', function() {
|
|
296
|
+
this.max('date').from('invoices');
|
|
297
|
+
});
|
|
298
|
+
records = await map(
|
|
299
|
+
records,
|
|
300
|
+
async (record) => {
|
|
301
|
+
const rec = self.processInvoice(record);
|
|
302
|
+
return rec;
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
} catch (e) {
|
|
306
|
+
throw e;
|
|
307
|
+
}
|
|
308
|
+
return records;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
*
|
|
313
|
+
* Returns all held invoices
|
|
314
|
+
* @returns{Promise<array>} held invoice objects
|
|
315
|
+
* @memberof FAMISDb
|
|
316
|
+
*/
|
|
317
|
+
async getHeldInvoices() {
|
|
318
|
+
const self = this;
|
|
319
|
+
let records;
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
records = await this.db('invoices')
|
|
323
|
+
.whereNull('deleted')
|
|
324
|
+
.andWhere('invoiced', '0');
|
|
325
|
+
records = await map(
|
|
326
|
+
records,
|
|
327
|
+
async (record) => {
|
|
328
|
+
const rec = self.processInvoice(record);
|
|
329
|
+
return rec;
|
|
330
|
+
}
|
|
331
|
+
);
|
|
332
|
+
} catch (e) {
|
|
333
|
+
throw e;
|
|
334
|
+
}
|
|
335
|
+
return records;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
*
|
|
340
|
+
* Returns all awaiting email invoices
|
|
341
|
+
* @returns{Promise<array>} awaiting email invoice objects
|
|
342
|
+
* @memberof FAMISDb
|
|
343
|
+
*/
|
|
344
|
+
async getInvoicesAwaitingEmail() {
|
|
345
|
+
const self = this;
|
|
346
|
+
let records;
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
records = await this.db('invoices')
|
|
350
|
+
.whereNull('deleted')
|
|
351
|
+
.andWhere('emailed', 'False');
|
|
352
|
+
records = await map(
|
|
353
|
+
records,
|
|
354
|
+
async (record) => {
|
|
355
|
+
const rec = self.processInvoice(record);
|
|
356
|
+
return rec;
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
} catch (e) {
|
|
360
|
+
throw e;
|
|
361
|
+
}
|
|
362
|
+
return records;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
*
|
|
367
|
+
* Search invoices by originating system generated invoice number
|
|
368
|
+
* @param {string} number
|
|
369
|
+
* @returns{Promise<array>} invoice objects
|
|
370
|
+
* @memberof FAMISDb
|
|
371
|
+
*/
|
|
372
|
+
async searchInvoicesByNumber(number) {
|
|
373
|
+
const self = this;
|
|
374
|
+
let records;
|
|
375
|
+
let retVal;
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
records = await this.db('invoices')
|
|
379
|
+
.whereNull('deleted')
|
|
380
|
+
.andWhere('number', 'ilike', `${number}%`);
|
|
381
|
+
if (records.length) {
|
|
382
|
+
retVal = await map(
|
|
383
|
+
records,
|
|
384
|
+
async (record) => {
|
|
385
|
+
const rec = self.processInvoice(record);
|
|
386
|
+
return rec;
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
} catch (e) {
|
|
391
|
+
throw e;
|
|
392
|
+
}
|
|
393
|
+
return retVal;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
*
|
|
398
|
+
* Search invoices by FAMIS invoice number
|
|
399
|
+
* @param {string} famis
|
|
400
|
+
* @returns{Promise<array>} invoice objects
|
|
401
|
+
* @memberof FAMISDb
|
|
402
|
+
*/
|
|
403
|
+
async searchInvoicesByFamis(famis) {
|
|
404
|
+
const self = this;
|
|
405
|
+
let records;
|
|
406
|
+
let retVal;
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
records = await this.db('invoices')
|
|
410
|
+
.whereNull('deleted')
|
|
411
|
+
.andWhere('famisInvoiceNumber', 'ilike', `${famis}%`);
|
|
412
|
+
if (records.length) {
|
|
413
|
+
retVal = await map(
|
|
414
|
+
records,
|
|
415
|
+
async (record) => {
|
|
416
|
+
const rec = self.processInvoice(record);
|
|
417
|
+
return rec;
|
|
418
|
+
}
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
} catch (e) {
|
|
422
|
+
throw e;
|
|
423
|
+
}
|
|
424
|
+
return retVal;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
*
|
|
429
|
+
* Adds an invoice
|
|
430
|
+
* @param {object} invoice
|
|
431
|
+
* @param {string} type
|
|
432
|
+
* @param {string} famisInvoiceNumber
|
|
433
|
+
* @returns{Promise<object>} invoice
|
|
434
|
+
* @memberof FAMISDb
|
|
435
|
+
*/
|
|
436
|
+
async addInvoice(invoice, type, famisInvoiceNumber) {
|
|
437
|
+
let records;
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
famisInvoiceNumber = (typeof famisInvoiceNumber === 'undefined') ? null : famisInvoiceNumber;
|
|
441
|
+
famisInvoiceNumber = famisInvoiceNumber ? famisInvoiceNumber : await this.getNextFAMISInvoiceNumber(type);
|
|
442
|
+
invoice.details.forEach(d => {
|
|
443
|
+
d.famis.invoiceNumber = famisInvoiceNumber;
|
|
444
|
+
if (!d.other) {
|
|
445
|
+
d.other = '0.00';
|
|
446
|
+
}
|
|
447
|
+
if (!d.comments) {
|
|
448
|
+
d.comments = null;
|
|
449
|
+
}
|
|
450
|
+
if (!d.billingPeriod) {
|
|
451
|
+
d.billingPeriod = null;
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
records = await this.db('invoices')
|
|
455
|
+
.returning(returningColumns)
|
|
456
|
+
.insert({
|
|
457
|
+
number: invoice.number,
|
|
458
|
+
lab: invoice.lab,
|
|
459
|
+
totalAmount: parseFloat(invoice.totalAmount),
|
|
460
|
+
data: JSON.stringify(invoice),
|
|
461
|
+
date: invoice.date,
|
|
462
|
+
uid: cuid(),
|
|
463
|
+
fingerprint: invoice.fingerprint,
|
|
464
|
+
type: type,
|
|
465
|
+
famisInvoiceNumber: famisInvoiceNumber,
|
|
466
|
+
customerAccount: this.getAccountNumber(invoice.details[0].famis).accountNumber,
|
|
467
|
+
sellingAccount: getCoreAccountNumber(invoice.details[0].famis),
|
|
468
|
+
});
|
|
469
|
+
} catch (e) {
|
|
470
|
+
throw e;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return records;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
*
|
|
478
|
+
* Updates an invoice
|
|
479
|
+
* @param {string} uid
|
|
480
|
+
* @param {object} invoice
|
|
481
|
+
* @returns{Promise<object>} invoice object
|
|
482
|
+
* @memberof FAMISDb
|
|
483
|
+
*/
|
|
484
|
+
async updateInvoice(uid, invoice) {
|
|
485
|
+
let records;
|
|
486
|
+
invoice.details.forEach(d => {
|
|
487
|
+
if (!d.other) {
|
|
488
|
+
d.other = '0.00';
|
|
489
|
+
}
|
|
490
|
+
if (!d.comments) {
|
|
491
|
+
d.comments = null;
|
|
492
|
+
}
|
|
493
|
+
if (!d.billingPeriod) {
|
|
494
|
+
d.billingPeriod = null;
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
try {
|
|
498
|
+
records = await this.db('invoices')
|
|
499
|
+
.returning(returningColumns)
|
|
500
|
+
.update({
|
|
501
|
+
number: invoice.number,
|
|
502
|
+
lab: invoice.lab,
|
|
503
|
+
totalAmount: parseFloat(invoice.totalAmount),
|
|
504
|
+
data: JSON.stringify(invoice),
|
|
505
|
+
date: invoice.date,
|
|
506
|
+
customerAccount: this.getAccountNumber(invoice.details[0].famis).accountNumber,
|
|
507
|
+
sellingAccount: getCoreAccountNumber(invoice.details[0].famis),
|
|
508
|
+
fingerprint: invoice.fingerprint ? invoice.fingerprint : null,
|
|
509
|
+
})
|
|
510
|
+
.where('uid', uid);
|
|
511
|
+
} catch (e) {
|
|
512
|
+
throw e;
|
|
513
|
+
}
|
|
514
|
+
return records;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
*
|
|
519
|
+
* Toggles an invoice as paid
|
|
520
|
+
* @param {string} uid
|
|
521
|
+
* @param {boolean} invoiced
|
|
522
|
+
* @returns{Promise<object>} invoice object
|
|
523
|
+
* @memberof FAMISDb
|
|
524
|
+
*/
|
|
525
|
+
async markInvoiced(uid, invoiced) {
|
|
526
|
+
let records;
|
|
527
|
+
|
|
528
|
+
try {
|
|
529
|
+
records = await this.db('invoices')
|
|
530
|
+
.returning(returningColumns)
|
|
531
|
+
.update({
|
|
532
|
+
invoiced,
|
|
533
|
+
})
|
|
534
|
+
.where('uid', uid);
|
|
535
|
+
} catch (e) {
|
|
536
|
+
throw e;
|
|
537
|
+
}
|
|
538
|
+
return records;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
*
|
|
543
|
+
* Toggles an invoice as emailed
|
|
544
|
+
* @param {string} uid
|
|
545
|
+
* @param {boolean} emailed
|
|
546
|
+
* @returns{Promise<object>} invoice object
|
|
547
|
+
* @memberof FAMISDb
|
|
548
|
+
*/
|
|
549
|
+
async markEmailed(uid, emailed) {
|
|
550
|
+
let records;
|
|
551
|
+
|
|
552
|
+
try {
|
|
553
|
+
records = await this.db('invoices')
|
|
554
|
+
.returning(returningColumns)
|
|
555
|
+
.update({
|
|
556
|
+
emailed,
|
|
557
|
+
})
|
|
558
|
+
.where('uid', uid);
|
|
559
|
+
} catch (e) {
|
|
560
|
+
throw e;
|
|
561
|
+
}
|
|
562
|
+
return records;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
*
|
|
567
|
+
* Sets an invoice's deletion date
|
|
568
|
+
* @param {string} uid
|
|
569
|
+
* @param {date} deleted
|
|
570
|
+
* @returns{Promise<object>} invoice object
|
|
571
|
+
* @memberof FAMISDb
|
|
572
|
+
*/
|
|
573
|
+
async setDeleted(uid, deleted) {
|
|
574
|
+
let records;
|
|
575
|
+
|
|
576
|
+
try {
|
|
577
|
+
records = await this.db('invoices')
|
|
578
|
+
.returning(returningColumns)
|
|
579
|
+
.update({
|
|
580
|
+
deleted,
|
|
581
|
+
})
|
|
582
|
+
.where('uid', uid);
|
|
583
|
+
} catch (e) {
|
|
584
|
+
throw e;
|
|
585
|
+
}
|
|
586
|
+
return records;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
*
|
|
591
|
+
* Sets an invoice's posted date
|
|
592
|
+
* @param {string} uid
|
|
593
|
+
* @param {date} postedDate
|
|
594
|
+
* @returns{Promise<object>} invoice object
|
|
595
|
+
* @memberof FAMISDb
|
|
596
|
+
*/
|
|
597
|
+
async setInvoicePostedDate(uid, postedDate) {
|
|
598
|
+
let records;
|
|
599
|
+
|
|
600
|
+
try {
|
|
601
|
+
records = await this.db('invoices')
|
|
602
|
+
.returning(returningColumns)
|
|
603
|
+
.update({
|
|
604
|
+
postedDate,
|
|
605
|
+
})
|
|
606
|
+
.where('uid', uid);
|
|
607
|
+
} catch (e) {
|
|
608
|
+
throw e;
|
|
609
|
+
}
|
|
610
|
+
return records;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
*
|
|
615
|
+
* Sets an invoice's fingerprint
|
|
616
|
+
* @param {string} uid
|
|
617
|
+
* @param {string} fingerprint
|
|
618
|
+
* @returns{Promise<object>} invoice object
|
|
619
|
+
* @memberof FAMISDb
|
|
620
|
+
*/
|
|
621
|
+
async setInvoiceFingerPrint(uid, fingerprint) {
|
|
622
|
+
let records;
|
|
623
|
+
|
|
624
|
+
try {
|
|
625
|
+
records = await this.db('invoices')
|
|
626
|
+
.returning(returningColumns)
|
|
627
|
+
.update({
|
|
628
|
+
fingerprint,
|
|
629
|
+
})
|
|
630
|
+
.where('uid', uid);
|
|
631
|
+
} catch (e) {
|
|
632
|
+
throw e;
|
|
633
|
+
}
|
|
634
|
+
return records;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
*
|
|
639
|
+
* Get PI accounts by account number
|
|
640
|
+
* @param {string} number
|
|
641
|
+
* @returns{Promise<object>} account object
|
|
642
|
+
* @memberof FAMISDb
|
|
643
|
+
*/
|
|
644
|
+
async getByAccountNumber(number) {
|
|
645
|
+
let records;
|
|
646
|
+
let retVal;
|
|
647
|
+
const self = this;
|
|
648
|
+
|
|
649
|
+
if (!number) console.log('Null account number');
|
|
650
|
+
try {
|
|
651
|
+
records = await this.db('accounts')
|
|
652
|
+
.leftJoin('departments',
|
|
653
|
+
function() {
|
|
654
|
+
this
|
|
655
|
+
.on('accounts.department', '=', 'departments.dept')
|
|
656
|
+
.on('departments.part', '=', self.db.raw('?', [number.substring(0,2)]))
|
|
657
|
+
}
|
|
658
|
+
)
|
|
659
|
+
.leftJoin('accounts as parent_account',
|
|
660
|
+
function() {
|
|
661
|
+
this
|
|
662
|
+
.on('accounts.campus_code', '=', 'parent_account.campus_code')
|
|
663
|
+
.on('accounts.account', '=', 'parent_account.account')
|
|
664
|
+
.on('parent_account.support_account', '=', self.db.raw('?', ["00000"]))
|
|
665
|
+
.on('accounts.fiscal_year', '=', 'parent_account.fiscal_year')
|
|
666
|
+
}
|
|
667
|
+
)
|
|
668
|
+
.select(
|
|
669
|
+
'accounts.*',
|
|
670
|
+
'departments.idt_email as billingEmailId',
|
|
671
|
+
'parent_account.idt_allowed as parent_account_idt_allowed'
|
|
672
|
+
)
|
|
673
|
+
.whereNull('departments.sub')
|
|
674
|
+
.andWhere(function() {
|
|
675
|
+
this.whereNull('departments.exp_date').orWhere('departments.exp_date', '>', self.db.raw('CURRENT_DATE'))
|
|
676
|
+
})
|
|
677
|
+
.andWhere('accounts.account_number', number);
|
|
678
|
+
if (records.length) {
|
|
679
|
+
retVal = records[0];
|
|
680
|
+
} else {
|
|
681
|
+
records = await this.db('customers')
|
|
682
|
+
.where('account_number', number);
|
|
683
|
+
if (records.length) {
|
|
684
|
+
retVal = Object.assign(
|
|
685
|
+
{},
|
|
686
|
+
records[0],
|
|
687
|
+
{
|
|
688
|
+
idt_allowed: false,
|
|
689
|
+
parent_account_idt_allowed: false,
|
|
690
|
+
billingEmailId: records[0].email,
|
|
691
|
+
}
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
} catch (e) {
|
|
696
|
+
throw e;
|
|
697
|
+
}
|
|
698
|
+
return retVal;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
*
|
|
703
|
+
* Search PI accounts by partial account number
|
|
704
|
+
* @param {string} number
|
|
705
|
+
* @returns{Promise<array>} account object(s)
|
|
706
|
+
* @memberof FAMISDb
|
|
707
|
+
*/
|
|
708
|
+
async searchByAccountNumber(number) {
|
|
709
|
+
let records;
|
|
710
|
+
const self = this;
|
|
711
|
+
|
|
712
|
+
try {
|
|
713
|
+
const pis = await this.db('accounts')
|
|
714
|
+
.leftJoin('departments',
|
|
715
|
+
function() {
|
|
716
|
+
this
|
|
717
|
+
.on('accounts.department', '=', 'departments.dept')
|
|
718
|
+
.on('departments.part', '=', self.db.raw('?', [number.substring(0,2)]))
|
|
719
|
+
}
|
|
720
|
+
)
|
|
721
|
+
.leftJoin('account_balances',
|
|
722
|
+
function() {
|
|
723
|
+
this
|
|
724
|
+
.on('account_balances.account_number', '=', 'accounts.budget_account_number')
|
|
725
|
+
.on('account_balances.fiscal_year', '=', 'accounts.fiscal_year')
|
|
726
|
+
}
|
|
727
|
+
)
|
|
728
|
+
.select('accounts.*', 'departments.idt_email as billingEmailId', 'account_balances.balance')
|
|
729
|
+
.whereNull('departments.sub')
|
|
730
|
+
.andWhere(function() {
|
|
731
|
+
this.whereNull('departments.exp_date').orWhere('departments.exp_date', '>', self.db.raw('CURRENT_DATE'))
|
|
732
|
+
})
|
|
733
|
+
.where('accounts.account_number', 'ilike', `%${number}%`);
|
|
734
|
+
const customers = await this.db('customers')
|
|
735
|
+
.where('customers.account_number', 'ilike', `%${number}%`)
|
|
736
|
+
.whereNotIn('customers.account_number', pis.map(p => p.account_number));
|
|
737
|
+
records = [...customers, ...pis];
|
|
738
|
+
} catch (e) {
|
|
739
|
+
throw e;
|
|
740
|
+
}
|
|
741
|
+
return records;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
*
|
|
746
|
+
* Get PI Accounts by UIN
|
|
747
|
+
* @param {string} uin
|
|
748
|
+
* @returns{Promise<array>} account object array
|
|
749
|
+
* @memberof FAMISDb
|
|
750
|
+
*/
|
|
751
|
+
async getAccountsByUin(uin) {
|
|
752
|
+
let records;
|
|
753
|
+
const self = this;
|
|
754
|
+
|
|
755
|
+
try {
|
|
756
|
+
records = await this.db('accounts')
|
|
757
|
+
.join('departments',
|
|
758
|
+
function() {
|
|
759
|
+
this
|
|
760
|
+
.on('accounts.department', '=', 'departments.dept')
|
|
761
|
+
.on('accounts.campus_code', '=', 'departments.part')
|
|
762
|
+
},
|
|
763
|
+
'left'
|
|
764
|
+
)
|
|
765
|
+
.select('accounts.*', 'departments.idt_email as billingEmailId')
|
|
766
|
+
.whereNull('departments.sub')
|
|
767
|
+
.andWhere(function() {
|
|
768
|
+
this.whereNull('departments.exp_date').orWhere('departments.exp_date', '>', self.db.raw('CURRENT_DATE'))
|
|
769
|
+
})
|
|
770
|
+
.where('uin', uin);
|
|
771
|
+
} catch (e) {
|
|
772
|
+
throw e;
|
|
773
|
+
}
|
|
774
|
+
return records;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
*
|
|
779
|
+
* Get PI accounts by responsible UIN
|
|
780
|
+
* @param {string} uin
|
|
781
|
+
* @returns{Promise<array>} account object array
|
|
782
|
+
* @memberof FAMISDb
|
|
783
|
+
*/
|
|
784
|
+
async getAccountsByResponsibleUin(uin) {
|
|
785
|
+
let records;
|
|
786
|
+
const self = this;
|
|
787
|
+
|
|
788
|
+
try {
|
|
789
|
+
records = await this.db('accounts')
|
|
790
|
+
.join('departments',
|
|
791
|
+
function() {
|
|
792
|
+
this
|
|
793
|
+
.on('accounts.department', '=', 'departments.dept')
|
|
794
|
+
.on('accounts.campus_code', '=', 'departments.part')
|
|
795
|
+
},
|
|
796
|
+
'left'
|
|
797
|
+
)
|
|
798
|
+
.select('accounts.*', 'departments.idt_email as billingEmailId')
|
|
799
|
+
.whereNull('departments.sub')
|
|
800
|
+
.andWhere(function() {
|
|
801
|
+
this.whereNull('departments.exp_date').orWhere('departments.exp_date', '>', self.db.raw('CURRENT_DATE'))
|
|
802
|
+
})
|
|
803
|
+
.where('responsible_uin', uin);
|
|
804
|
+
} catch (e) {
|
|
805
|
+
throw e;
|
|
806
|
+
}
|
|
807
|
+
return records;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
*
|
|
812
|
+
* Get next FAMIS invoice number in sequence for specified invoice type
|
|
813
|
+
* @param {string} invoice
|
|
814
|
+
* @returns{Promise<string>} next invoice number in sequence
|
|
815
|
+
* @memberof FAMISDb
|
|
816
|
+
*/
|
|
817
|
+
async getNextFAMISInvoiceNumber(type) {
|
|
818
|
+
let records;
|
|
819
|
+
let prefix;
|
|
820
|
+
let retVal;
|
|
821
|
+
famisInvoiceNumbers.forEach(f => {
|
|
822
|
+
if (f.aliases.includes(type)) {
|
|
823
|
+
prefix = f;
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
try {
|
|
827
|
+
retVal = `${prefix.prefix}${prefix.start}`;
|
|
828
|
+
records = await this.db('invoices').whereIn('type', prefix.aliases).orderBy('uid', 'desc');
|
|
829
|
+
if (records.length) {
|
|
830
|
+
const number = parseInt(records[0].famisInvoiceNumber.substring(1), 10) + 1;
|
|
831
|
+
retVal = `${prefix.prefix}${number}`;
|
|
832
|
+
}
|
|
833
|
+
} catch (e) {
|
|
834
|
+
throw e;
|
|
835
|
+
}
|
|
836
|
+
return retVal;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
*
|
|
841
|
+
* Get matching expense code for specified revenue code
|
|
842
|
+
* @param {string} revenue
|
|
843
|
+
* @returns{Promise<string>} expense code
|
|
844
|
+
* @memberof FAMISDb
|
|
845
|
+
*/
|
|
846
|
+
async getExpenseCode(revenue) {
|
|
847
|
+
let records;
|
|
848
|
+
let retVal;
|
|
849
|
+
try {
|
|
850
|
+
records = await this.db('codes').where('revenue', revenue).orderBy('id', 'desc');
|
|
851
|
+
if (records.length) {
|
|
852
|
+
retVal = records[0].expense;
|
|
853
|
+
}
|
|
854
|
+
} catch (e) {
|
|
855
|
+
throw e;
|
|
856
|
+
}
|
|
857
|
+
return retVal;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
*
|
|
862
|
+
* Get revenue code object
|
|
863
|
+
* @param {string} revenue
|
|
864
|
+
* @returns{Promise<object>} revenue code object
|
|
865
|
+
* @memberof FAMISDb
|
|
866
|
+
*/
|
|
867
|
+
async getRevenueCode(revenue) {
|
|
868
|
+
let records;
|
|
869
|
+
let retVal;
|
|
870
|
+
try {
|
|
871
|
+
records = await this.db('codes').where('revenue', revenue).orderBy('id', 'desc');
|
|
872
|
+
if (records.length) {
|
|
873
|
+
retVal = records[0];
|
|
874
|
+
}
|
|
875
|
+
} catch (e) {
|
|
876
|
+
throw e;
|
|
877
|
+
}
|
|
878
|
+
return retVal;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
*
|
|
883
|
+
* Check if expense code is within the allowable range for specified account and the specified fiscal year (defaults to current)
|
|
884
|
+
* @param {string} account
|
|
885
|
+
* @param {string} code
|
|
886
|
+
* @param {integer} fiscal
|
|
887
|
+
* @returns{Promise<boolean>} truthy value
|
|
888
|
+
* @memberof FAMISDb
|
|
889
|
+
*/
|
|
890
|
+
async allowableExpenseCode(accountNumber, code, fiscal) {
|
|
891
|
+
let records;
|
|
892
|
+
let retVal = false;
|
|
893
|
+
let d = new Date();
|
|
894
|
+
let year = fiscal ? fiscal : this.getCurrentFiscalYear();
|
|
895
|
+
try {
|
|
896
|
+
records = await this.db('subcode_edits')
|
|
897
|
+
.select('allowed')
|
|
898
|
+
.where('campus_code', accountNumber.substring(0, 2))
|
|
899
|
+
.andWhere('account', accountNumber.substring(2, 8))
|
|
900
|
+
.andWhere('support_account', accountNumber.substring(8, 13))
|
|
901
|
+
.andWhere('fiscal_year', year)
|
|
902
|
+
.andWhereRaw('? BETWEEN low AND high', [parseInt(code)]);
|
|
903
|
+
if (records.length) {
|
|
904
|
+
retVal = records[0].allowed;
|
|
905
|
+
} else {
|
|
906
|
+
records = await this.db('abr_rules')
|
|
907
|
+
.select('abr_rules.*')
|
|
908
|
+
.join('accounts',
|
|
909
|
+
function() {
|
|
910
|
+
this
|
|
911
|
+
.on('accounts.abr_rule', '=', 'abr_rules.abr_rule')
|
|
912
|
+
.on('accounts.campus_code', '=', 'abr_rules.campus_code')
|
|
913
|
+
.on('accounts.fiscal_year', '=', 'abr_rules.fiscal_year')
|
|
914
|
+
},
|
|
915
|
+
'left'
|
|
916
|
+
)
|
|
917
|
+
.where('accounts.account_number', accountNumber)
|
|
918
|
+
.andWhere('accounts.fiscal_year', year)
|
|
919
|
+
.andWhereRaw('? BETWEEN abr_rules.low AND abr_rules.high', [parseInt(code)]);
|
|
920
|
+
if (records.length) {
|
|
921
|
+
retVal = true;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
} catch (e) {
|
|
925
|
+
throw e;
|
|
926
|
+
}
|
|
927
|
+
return retVal;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
*
|
|
932
|
+
* Get allowable expense code ranges for specified account
|
|
933
|
+
* @param {string} account
|
|
934
|
+
* @returns{Promise<boolean>} expense code range object(s)
|
|
935
|
+
* @memberof FAMISDb
|
|
936
|
+
*/
|
|
937
|
+
async getAllowableExpenseCodes(accountNumber, fiscal) {
|
|
938
|
+
let abr;
|
|
939
|
+
let subcode;
|
|
940
|
+
let disallowed;
|
|
941
|
+
let retVal = {
|
|
942
|
+
allowed: [],
|
|
943
|
+
disallowed: [],
|
|
944
|
+
};
|
|
945
|
+
let year = fiscal ? fiscal : this.getCurrentFiscalYear();
|
|
946
|
+
try {
|
|
947
|
+
subcode = await this.db('subcode_edits')
|
|
948
|
+
.select('low', 'high')
|
|
949
|
+
.where('campus_code', accountNumber.substring(0, 2))
|
|
950
|
+
.andWhere('account', accountNumber.substring(2, 8))
|
|
951
|
+
.andWhere('support_account', accountNumber.substring(8, 13))
|
|
952
|
+
.andWhere('fiscal_year', year)
|
|
953
|
+
.andWhere('allowed', true)
|
|
954
|
+
.orderBy('low', 'asc');
|
|
955
|
+
disallowed = await this.db('subcode_edits')
|
|
956
|
+
.select('low', 'high')
|
|
957
|
+
.where('campus_code', accountNumber.substring(0, 2))
|
|
958
|
+
.andWhere('account', accountNumber.substring(2, 8))
|
|
959
|
+
.andWhere('support_account', accountNumber.substring(8, 13))
|
|
960
|
+
.andWhere('fiscal_year', year)
|
|
961
|
+
.andWhere('allowed', false)
|
|
962
|
+
.orderBy('low', 'asc');
|
|
963
|
+
abr = await this.db('abr_rules')
|
|
964
|
+
.select('abr_rules.low', 'abr_rules.high')
|
|
965
|
+
.join('accounts',
|
|
966
|
+
function() {
|
|
967
|
+
this
|
|
968
|
+
.on('accounts.abr_rule', '=', 'abr_rules.abr_rule')
|
|
969
|
+
.on('accounts.campus_code', '=', 'abr_rules.campus_code')
|
|
970
|
+
.on('accounts.fiscal_year', '=', 'abr_rules.fiscal_year')
|
|
971
|
+
},
|
|
972
|
+
'left'
|
|
973
|
+
)
|
|
974
|
+
.where('accounts.account_number', accountNumber)
|
|
975
|
+
.andWhere('accounts.fiscal_year', year)
|
|
976
|
+
.orderBy('low', 'asc');
|
|
977
|
+
if (abr.length || subcode.length) {
|
|
978
|
+
const allowed = [...abr, ...subcode];
|
|
979
|
+
retVal = {
|
|
980
|
+
allowed,
|
|
981
|
+
disallowed,
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
} catch (e) {
|
|
985
|
+
throw e;
|
|
986
|
+
}
|
|
987
|
+
return retVal;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
*
|
|
992
|
+
* Get core by id
|
|
993
|
+
* @param {string} id
|
|
994
|
+
* @returns{Promise<object>} core object
|
|
995
|
+
* @memberof FAMISDb
|
|
996
|
+
*/
|
|
997
|
+
async getCoreById(id) {
|
|
998
|
+
let records;
|
|
999
|
+
let retVal;
|
|
1000
|
+
try {
|
|
1001
|
+
const maxDates = this.db('core_accounts')
|
|
1002
|
+
.select(
|
|
1003
|
+
'coreId'
|
|
1004
|
+
)
|
|
1005
|
+
.max('effective', {as: 'effective'})
|
|
1006
|
+
.groupBy('coreId')
|
|
1007
|
+
.where('effective', '<=', this.db.raw('now()'))
|
|
1008
|
+
.as("max_dates");
|
|
1009
|
+
const accounts = this.db('core_accounts')
|
|
1010
|
+
.select(
|
|
1011
|
+
'core_accounts.coreId',
|
|
1012
|
+
'core_accounts.account',
|
|
1013
|
+
'core_accounts.effective',
|
|
1014
|
+
)
|
|
1015
|
+
.join(maxDates,
|
|
1016
|
+
function() {
|
|
1017
|
+
this
|
|
1018
|
+
.on('core_accounts.coreId', '=', 'max_dates.coreId')
|
|
1019
|
+
.andOn('core_accounts.effective', '=', 'max_dates.effective')
|
|
1020
|
+
}
|
|
1021
|
+
)
|
|
1022
|
+
.as("core_accounts");
|
|
1023
|
+
records = await this.db('cores')
|
|
1024
|
+
.select(
|
|
1025
|
+
'cores.id',
|
|
1026
|
+
'core_accounts.account',
|
|
1027
|
+
'core_accounts.effective',
|
|
1028
|
+
'cores.facility',
|
|
1029
|
+
'cores.contact',
|
|
1030
|
+
'cores.contactEmail',
|
|
1031
|
+
'cores.systemId',
|
|
1032
|
+
'system.name as partName',
|
|
1033
|
+
'system.part as partNumber',
|
|
1034
|
+
'cores.enabled',
|
|
1035
|
+
'cores.deleted',
|
|
1036
|
+
'cores.coreEmail',
|
|
1037
|
+
'cores.corePhone',
|
|
1038
|
+
'cores.coreAddress',
|
|
1039
|
+
'cores.from_email',
|
|
1040
|
+
'cores.invoice_email'
|
|
1041
|
+
)
|
|
1042
|
+
.join(accounts, 'cores.id', '=', 'core_accounts.coreId')
|
|
1043
|
+
.join('system', 'cores.systemId', '=', 'system.id')
|
|
1044
|
+
.where('cores.id', id)
|
|
1045
|
+
.orderBy('cores.id', 'desc');
|
|
1046
|
+
if (records.length) {
|
|
1047
|
+
retVal = records[0];
|
|
1048
|
+
}
|
|
1049
|
+
} catch (e) {
|
|
1050
|
+
throw e;
|
|
1051
|
+
}
|
|
1052
|
+
return retVal;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
*
|
|
1057
|
+
* Get core by account number
|
|
1058
|
+
* @param {string} account
|
|
1059
|
+
* @returns{Promise<object>} core object
|
|
1060
|
+
* @memberof FAMISDb
|
|
1061
|
+
*/
|
|
1062
|
+
async getCoreByAccount(account) {
|
|
1063
|
+
let records;
|
|
1064
|
+
let retVal;
|
|
1065
|
+
try {
|
|
1066
|
+
records = await this.db('cores')
|
|
1067
|
+
.select(
|
|
1068
|
+
'cores.id',
|
|
1069
|
+
'core_accounts.account',
|
|
1070
|
+
'core_accounts.effective',
|
|
1071
|
+
'cores.facility',
|
|
1072
|
+
'cores.contact',
|
|
1073
|
+
'cores.contactEmail',
|
|
1074
|
+
'cores.systemId',
|
|
1075
|
+
'cores.enabled',
|
|
1076
|
+
'cores.deleted',
|
|
1077
|
+
'cores.coreEmail',
|
|
1078
|
+
'cores.corePhone',
|
|
1079
|
+
'cores.coreAddress',
|
|
1080
|
+
'cores.from_email',
|
|
1081
|
+
'cores.invoice_email'
|
|
1082
|
+
)
|
|
1083
|
+
.join('core_accounts', 'cores.id', '=', 'core_accounts.coreId')
|
|
1084
|
+
.where('core_accounts.account', account)
|
|
1085
|
+
.orderBy('cores.id', 'desc');
|
|
1086
|
+
if (records.length) {
|
|
1087
|
+
retVal = records[0];
|
|
1088
|
+
}
|
|
1089
|
+
} catch (e) {
|
|
1090
|
+
throw e;
|
|
1091
|
+
}
|
|
1092
|
+
return retVal;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
*
|
|
1097
|
+
* Get core current revenue account number
|
|
1098
|
+
* @param {string} coreId
|
|
1099
|
+
* @returns{Promise<object>} core object
|
|
1100
|
+
* @memberof FAMISDb
|
|
1101
|
+
*/
|
|
1102
|
+
async getCoreCurrentAccount(coreId) {
|
|
1103
|
+
let records;
|
|
1104
|
+
let retVal;
|
|
1105
|
+
try {
|
|
1106
|
+
const maxDates = this.db('core_accounts')
|
|
1107
|
+
.select(
|
|
1108
|
+
'coreId'
|
|
1109
|
+
)
|
|
1110
|
+
.max('effective', {as: 'effective'})
|
|
1111
|
+
.groupBy('coreId')
|
|
1112
|
+
.where('effective', '<=', this.db.raw('now()'))
|
|
1113
|
+
.andWhere('coreId', '=', coreId)
|
|
1114
|
+
.as("max_dates");
|
|
1115
|
+
const accounts = this.db('core_accounts')
|
|
1116
|
+
.select(
|
|
1117
|
+
'core_accounts.coreId',
|
|
1118
|
+
'core_accounts.account',
|
|
1119
|
+
'core_accounts.effective',
|
|
1120
|
+
)
|
|
1121
|
+
.join(maxDates,
|
|
1122
|
+
function() {
|
|
1123
|
+
this
|
|
1124
|
+
.on('core_accounts.coreId', '=', 'max_dates.coreId')
|
|
1125
|
+
.andOn('core_accounts.effective', '=', 'max_dates.effective')
|
|
1126
|
+
}
|
|
1127
|
+
)
|
|
1128
|
+
.as("core_accounts");
|
|
1129
|
+
records = await this.db('cores')
|
|
1130
|
+
.select(
|
|
1131
|
+
'cores.id',
|
|
1132
|
+
'core_accounts.account',
|
|
1133
|
+
'core_accounts.effective',
|
|
1134
|
+
'cores.facility',
|
|
1135
|
+
'cores.contact',
|
|
1136
|
+
'cores.contactEmail',
|
|
1137
|
+
'cores.systemId',
|
|
1138
|
+
'cores.enabled',
|
|
1139
|
+
'cores.deleted',
|
|
1140
|
+
'cores.coreEmail',
|
|
1141
|
+
'cores.corePhone',
|
|
1142
|
+
'cores.coreAddress',
|
|
1143
|
+
'cores.from_email',
|
|
1144
|
+
'cores.invoice_email'
|
|
1145
|
+
)
|
|
1146
|
+
.join(accounts, 'cores.id', '=', 'core_accounts.coreId')
|
|
1147
|
+
.orderBy('cores.id', 'desc');
|
|
1148
|
+
if (records.length) {
|
|
1149
|
+
retVal = records[0];
|
|
1150
|
+
}
|
|
1151
|
+
} catch (e) {
|
|
1152
|
+
throw e;
|
|
1153
|
+
}
|
|
1154
|
+
return retVal;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
*
|
|
1159
|
+
* Get system part by ID
|
|
1160
|
+
* @param {number} id
|
|
1161
|
+
* @returns{Promise<object>} system part object
|
|
1162
|
+
* @memberof FAMISDb
|
|
1163
|
+
*/
|
|
1164
|
+
async getSystemById(id) {
|
|
1165
|
+
let records;
|
|
1166
|
+
let retVal;
|
|
1167
|
+
try {
|
|
1168
|
+
records = await this.db('system').where('id', id).orderBy('id', 'desc');
|
|
1169
|
+
if (records.length) {
|
|
1170
|
+
retVal = records[0];
|
|
1171
|
+
}
|
|
1172
|
+
} catch (e) {
|
|
1173
|
+
throw e;
|
|
1174
|
+
}
|
|
1175
|
+
return retVal;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
/**
|
|
1179
|
+
*
|
|
1180
|
+
* Get system part by two digit part number
|
|
1181
|
+
* @param {string} number
|
|
1182
|
+
* @returns{Promise<object>} system part object
|
|
1183
|
+
* @memberof FAMISDb
|
|
1184
|
+
*/
|
|
1185
|
+
async getSystemByPartNumber(number) {
|
|
1186
|
+
let records;
|
|
1187
|
+
let retVal;
|
|
1188
|
+
try {
|
|
1189
|
+
records = await this.db('system').where('part', number).orderBy('id', 'desc');
|
|
1190
|
+
if (records.length) {
|
|
1191
|
+
retVal = records[0];
|
|
1192
|
+
}
|
|
1193
|
+
} catch (e) {
|
|
1194
|
+
throw e;
|
|
1195
|
+
}
|
|
1196
|
+
return retVal;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
/**
|
|
1200
|
+
*
|
|
1201
|
+
* Get all enabled cores
|
|
1202
|
+
* @param {string} date
|
|
1203
|
+
* @returns{Promise<array>} core objects
|
|
1204
|
+
* @memberof FAMISDb
|
|
1205
|
+
*/
|
|
1206
|
+
async getEnabledCores(date) {
|
|
1207
|
+
let records;
|
|
1208
|
+
let retVal = [];
|
|
1209
|
+
try {
|
|
1210
|
+
const maxDates = this.db('core_accounts')
|
|
1211
|
+
.select(
|
|
1212
|
+
'coreId'
|
|
1213
|
+
)
|
|
1214
|
+
.max('effective', {as: 'effective'})
|
|
1215
|
+
.groupBy('coreId')
|
|
1216
|
+
.where('effective', '<=', date ? date : this.db.raw('now()'))
|
|
1217
|
+
.as("max_dates");
|
|
1218
|
+
const accounts = this.db('core_accounts')
|
|
1219
|
+
.select(
|
|
1220
|
+
'core_accounts.coreId',
|
|
1221
|
+
'core_accounts.account',
|
|
1222
|
+
'core_accounts.effective',
|
|
1223
|
+
)
|
|
1224
|
+
.join(maxDates,
|
|
1225
|
+
function() {
|
|
1226
|
+
this
|
|
1227
|
+
.on('core_accounts.coreId', '=', 'max_dates.coreId')
|
|
1228
|
+
.andOn('core_accounts.effective', '=', 'max_dates.effective')
|
|
1229
|
+
}
|
|
1230
|
+
)
|
|
1231
|
+
.as("core_accounts");
|
|
1232
|
+
records = await this.db('cores')
|
|
1233
|
+
.select(
|
|
1234
|
+
'cores.id',
|
|
1235
|
+
'core_accounts.account',
|
|
1236
|
+
'core_accounts.effective',
|
|
1237
|
+
'cores.facility',
|
|
1238
|
+
'cores.contact',
|
|
1239
|
+
'cores.contactEmail',
|
|
1240
|
+
'cores.systemId',
|
|
1241
|
+
'cores.enabled',
|
|
1242
|
+
'cores.deleted',
|
|
1243
|
+
'cores.coreEmail',
|
|
1244
|
+
'cores.corePhone',
|
|
1245
|
+
'cores.coreAddress',
|
|
1246
|
+
'cores.from_email',
|
|
1247
|
+
'cores.invoice_email'
|
|
1248
|
+
)
|
|
1249
|
+
.join(accounts, 'cores.id', '=', 'core_accounts.coreId')
|
|
1250
|
+
.where('enabled', '1')
|
|
1251
|
+
.orderBy('cores.id', 'desc');
|
|
1252
|
+
if (records.length) {
|
|
1253
|
+
retVal = records;
|
|
1254
|
+
}
|
|
1255
|
+
} catch (e) {
|
|
1256
|
+
throw e;
|
|
1257
|
+
}
|
|
1258
|
+
return retVal;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
*
|
|
1263
|
+
* Get all cores
|
|
1264
|
+
* @returns{Promise<array>} core objects
|
|
1265
|
+
* @memberof FAMISDb
|
|
1266
|
+
*/
|
|
1267
|
+
async getAllCores() {
|
|
1268
|
+
let records;
|
|
1269
|
+
let retVal = [];
|
|
1270
|
+
try {
|
|
1271
|
+
records = await this.db('cores').orderBy('id', 'asc');
|
|
1272
|
+
if (records.length) {
|
|
1273
|
+
retVal = records;
|
|
1274
|
+
}
|
|
1275
|
+
} catch (e) {
|
|
1276
|
+
throw e;
|
|
1277
|
+
}
|
|
1278
|
+
return retVal;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
/**
|
|
1282
|
+
*
|
|
1283
|
+
* Get a core's users by core ID
|
|
1284
|
+
* @param {number} coreId
|
|
1285
|
+
* @returns{Promise<array>} user objects
|
|
1286
|
+
* @memberof FAMISDb
|
|
1287
|
+
*/
|
|
1288
|
+
async getCoreUsers(coreId) {
|
|
1289
|
+
let retVal;
|
|
1290
|
+
try {
|
|
1291
|
+
retVal = await this.db('user_cores')
|
|
1292
|
+
.join('users', 'user_cores.userId', '=', 'users.id')
|
|
1293
|
+
.where('coreId', coreId)
|
|
1294
|
+
.select(
|
|
1295
|
+
'users.*',
|
|
1296
|
+
'user_cores.id as userCoreId',
|
|
1297
|
+
'user_cores.coreId',
|
|
1298
|
+
'user_cores.email',
|
|
1299
|
+
);
|
|
1300
|
+
} catch (e) {
|
|
1301
|
+
throw e;
|
|
1302
|
+
}
|
|
1303
|
+
return retVal;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/**
|
|
1307
|
+
*
|
|
1308
|
+
* Get a user's cores they manage
|
|
1309
|
+
* @param {string} userId
|
|
1310
|
+
* @returns{Promise<array>} core objects
|
|
1311
|
+
* @memberof FAMISDb
|
|
1312
|
+
*/
|
|
1313
|
+
async getUserCores(userId) {
|
|
1314
|
+
let retVal;
|
|
1315
|
+
try {
|
|
1316
|
+
retVal = await this.db('user_cores')
|
|
1317
|
+
.join('cores', 'user_cores.coreId', '=', 'cores.id')
|
|
1318
|
+
.join('users', 'user_cores.userId', '=', 'users.id')
|
|
1319
|
+
.where('users.userId', userId)
|
|
1320
|
+
.select(
|
|
1321
|
+
'cores.*',
|
|
1322
|
+
'user_cores.id as userCoreId',
|
|
1323
|
+
'user_cores.userId',
|
|
1324
|
+
'user_cores.email',
|
|
1325
|
+
);
|
|
1326
|
+
} catch (e) {
|
|
1327
|
+
throw e;
|
|
1328
|
+
}
|
|
1329
|
+
return retVal;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
/**
|
|
1333
|
+
*
|
|
1334
|
+
* Get a system part's users by system ID
|
|
1335
|
+
* @param {number} systemId
|
|
1336
|
+
* @returns{Promise<array>} user objects
|
|
1337
|
+
* @memberof FAMISDb
|
|
1338
|
+
*/
|
|
1339
|
+
async getSystemUsers(systemId) {
|
|
1340
|
+
let retVal;
|
|
1341
|
+
try {
|
|
1342
|
+
retVal = await this.db('user_systems')
|
|
1343
|
+
.join('users', 'user_systems.userId', '=', 'users.id')
|
|
1344
|
+
.where('systemId', systemId)
|
|
1345
|
+
.select(
|
|
1346
|
+
'users.*',
|
|
1347
|
+
'user_systems.id as userSystemId',
|
|
1348
|
+
'user_systems.systemId',
|
|
1349
|
+
'user_systems.email',
|
|
1350
|
+
);
|
|
1351
|
+
} catch (e) {
|
|
1352
|
+
throw e;
|
|
1353
|
+
}
|
|
1354
|
+
return retVal;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
*
|
|
1359
|
+
* Get a user's system parts they manage
|
|
1360
|
+
* @param {*} userId
|
|
1361
|
+
* @returns{Promise<object>} system objects
|
|
1362
|
+
* @memberof FAMISDb
|
|
1363
|
+
*/
|
|
1364
|
+
async getUserSystemParts(userId) {
|
|
1365
|
+
let records;
|
|
1366
|
+
let retVal;
|
|
1367
|
+
try {
|
|
1368
|
+
retVal = await this.db('user_systems')
|
|
1369
|
+
.join('system', 'user_systems.systemId', '=', 'system.id')
|
|
1370
|
+
.join('users', 'user_systems.userId', '=', 'users.id')
|
|
1371
|
+
.where('users.userId', userId)
|
|
1372
|
+
.select(
|
|
1373
|
+
'system.*',
|
|
1374
|
+
'user_systems.id as userSystemId',
|
|
1375
|
+
'user_systems.userId',
|
|
1376
|
+
'user_systems.email',
|
|
1377
|
+
);
|
|
1378
|
+
} catch (e) {
|
|
1379
|
+
throw e;
|
|
1380
|
+
}
|
|
1381
|
+
return retVal;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
/**
|
|
1385
|
+
*
|
|
1386
|
+
* Get all system parts
|
|
1387
|
+
* @returns{Promise<object>} system objects
|
|
1388
|
+
* @memberof FAMISDb
|
|
1389
|
+
*/
|
|
1390
|
+
async getAllSystemParts() {
|
|
1391
|
+
let records;
|
|
1392
|
+
let retVal;
|
|
1393
|
+
try {
|
|
1394
|
+
retVal = await this.db('system').orderBy('id', 'asc');
|
|
1395
|
+
} catch (e) {
|
|
1396
|
+
throw e;
|
|
1397
|
+
}
|
|
1398
|
+
return retVal;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
/**
|
|
1402
|
+
*
|
|
1403
|
+
* Get user
|
|
1404
|
+
* @param {string} userId
|
|
1405
|
+
* @returns{Promise<object>} user object
|
|
1406
|
+
* @memberof FAMISDb
|
|
1407
|
+
*/
|
|
1408
|
+
async getUser(userId) {
|
|
1409
|
+
let records;
|
|
1410
|
+
let retVal;
|
|
1411
|
+
try {
|
|
1412
|
+
records = await this.db('users').where('userId', userId);
|
|
1413
|
+
if (records.length) {
|
|
1414
|
+
retVal = records[0];
|
|
1415
|
+
retVal.cores = await this.getUserCores(userId);
|
|
1416
|
+
retVal.systems = await this.getUserSystemParts(userId);
|
|
1417
|
+
}
|
|
1418
|
+
} catch (e) {
|
|
1419
|
+
throw e;
|
|
1420
|
+
}
|
|
1421
|
+
return retVal;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
/**
|
|
1425
|
+
*
|
|
1426
|
+
* Get customer by customer account number
|
|
1427
|
+
* @param {string} accountNumber
|
|
1428
|
+
* @param {string} campusCode
|
|
1429
|
+
* @returns{Promise<object>} customer account object
|
|
1430
|
+
* @memberof FAMISDb
|
|
1431
|
+
*/
|
|
1432
|
+
async getCustomerNumber(accountNumber, campusCode) {
|
|
1433
|
+
let records;
|
|
1434
|
+
let retVal;
|
|
1435
|
+
const code = pad(campusCode, 2);
|
|
1436
|
+
|
|
1437
|
+
try {
|
|
1438
|
+
records = await this.db('customers')
|
|
1439
|
+
.where('uid', `${code}${accountNumber}`);
|
|
1440
|
+
if (records.length) {
|
|
1441
|
+
retVal = records[0];
|
|
1442
|
+
}
|
|
1443
|
+
} catch (e) {
|
|
1444
|
+
throw e;
|
|
1445
|
+
}
|
|
1446
|
+
return retVal;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
/**
|
|
1450
|
+
*
|
|
1451
|
+
* Get current fiscal year
|
|
1452
|
+
* @returns{<integer>} fiscal year
|
|
1453
|
+
* @memberof FAMISDb
|
|
1454
|
+
*/
|
|
1455
|
+
getCurrentFiscalYear() {
|
|
1456
|
+
let year;
|
|
1457
|
+
let today = new Date();
|
|
1458
|
+
if ((today.getMonth() + 1) >= 9) {
|
|
1459
|
+
year = today.getFullYear() + 1;
|
|
1460
|
+
} else {
|
|
1461
|
+
year = today.getFullYear();
|
|
1462
|
+
}
|
|
1463
|
+
return year;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
/**
|
|
1467
|
+
*
|
|
1468
|
+
* Return fingerprint of invoice
|
|
1469
|
+
* @param {object} invoice json object
|
|
1470
|
+
* @returns{<string>} fingerprint
|
|
1471
|
+
* @memberof FAMISDb
|
|
1472
|
+
*/
|
|
1473
|
+
getInvoiceFingerPrint(invoice) {
|
|
1474
|
+
const fingerPrint = revisionHash(
|
|
1475
|
+
JSON.stringify(
|
|
1476
|
+
sortObjectKeys(invoice),
|
|
1477
|
+
(k, v) => undefined ? "undef" : v
|
|
1478
|
+
)
|
|
1479
|
+
);
|
|
1480
|
+
return fingerPrint;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
/**
|
|
1484
|
+
*
|
|
1485
|
+
* Return formatted account number
|
|
1486
|
+
* @param {string} accountNumber unformatted 13 digit famis account number
|
|
1487
|
+
* @returns{<string>} formatted 13 digits famis account number
|
|
1488
|
+
* @memberof FAMISDb
|
|
1489
|
+
*/
|
|
1490
|
+
formatAccountNumber(accountNumber) {
|
|
1491
|
+
const formatted = accountNumber.replace(/(\d{2})(\d{6})(\d{5})/, "$1-$2-$3");
|
|
1492
|
+
|
|
1493
|
+
return formatted;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
module.exports = FAMISDb;
|