@tamu-dor/famis-db 3.0.0-beta.1

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/dist/main.cjs ADDED
@@ -0,0 +1,2143 @@
1
+ 'use strict';
2
+
3
+ const knex = require('knex');
4
+ const cuid2 = require('@paralleldrive/cuid2');
5
+ const awaity = require('awaity');
6
+ const revisionHash = require('rev-hash');
7
+ const knexPaginate = require('knex-paginate');
8
+ require('knex-with-relations');
9
+
10
+ knexPaginate.attachPaginate();
11
+ const systemPartFields = [
12
+ "id",
13
+ "name",
14
+ "due",
15
+ "fein",
16
+ "agency",
17
+ "checkAddress",
18
+ "credit",
19
+ "creditUrl",
20
+ "creditFee",
21
+ "part",
22
+ "abbrev",
23
+ "aggiebuy",
24
+ "image"
25
+ ];
26
+ const invoiceFields = [
27
+ "uid",
28
+ "number",
29
+ "lab",
30
+ "totalAmount",
31
+ "date",
32
+ "data",
33
+ "invoiced",
34
+ "type",
35
+ "famisInvoiceNumber",
36
+ "deleted",
37
+ "customerAccount",
38
+ "sellingAccount",
39
+ "postedDate",
40
+ "emailed",
41
+ "replace_data"
42
+ ];
43
+ const accountFields = [
44
+ "uid",
45
+ "account_number",
46
+ "campus_code",
47
+ "fiscal_year",
48
+ "account",
49
+ "support_account",
50
+ "budget_support_account",
51
+ "budget_account_number",
52
+ "title",
53
+ "expiration_date",
54
+ "status",
55
+ "uin",
56
+ "responsible_uin",
57
+ "department",
58
+ "setup_date",
59
+ "budget_access",
60
+ "abr_rule",
61
+ "idt_allowed",
62
+ "adloc_campus_code",
63
+ "adloc_department",
64
+ "adloc_name",
65
+ "preferred_first_name",
66
+ "first_name",
67
+ "middle_name",
68
+ "last_name",
69
+ "email",
70
+ "work_phone",
71
+ "active",
72
+ "default_bank",
73
+ "bank_override",
74
+ "srs",
75
+ "cfda_no",
76
+ "sponsor_type"
77
+ ];
78
+ const departmentFields = [
79
+ "part",
80
+ "dept",
81
+ "sub",
82
+ "dept_name",
83
+ "dept_head",
84
+ "exec_level",
85
+ "division",
86
+ "school",
87
+ "exp_date",
88
+ "idt_email",
89
+ "id"
90
+ ];
91
+ const accountBalanceFields = [
92
+ "account_number",
93
+ "campus_code",
94
+ "fiscal_year",
95
+ "account",
96
+ "support_account",
97
+ "description",
98
+ "balance"
99
+ ];
100
+ const personFields = [
101
+ "tamuEduPersonUIN",
102
+ "tamuEduPersonNetID",
103
+ "uid",
104
+ "sn",
105
+ "givenName",
106
+ "cn",
107
+ "displayName",
108
+ "tamuEduPersonOfficialName",
109
+ "mail",
110
+ "postalAddress",
111
+ "mailStop",
112
+ "localityName",
113
+ "stateOrProvinceName",
114
+ "postalCode",
115
+ "telephoneNumber",
116
+ "title",
117
+ "tamuEduPersonTitleCode",
118
+ "tamuEduPersonDepartmentName",
119
+ "tamuEduPersonPrimaryMember",
120
+ "tamuEduPersonMember",
121
+ "tamuEduPersonPrimaryMemberName",
122
+ "tamuEduPersonAffiliation",
123
+ "eduPersonOrcid",
124
+ "tamuEduDataFeed",
125
+ "tamuLastSeenTimestamp",
126
+ "tamuEduPersonSupervisorUIN",
127
+ "tamuManualAddRDN"
128
+ ];
129
+ const famisInvoiceNumbers = [
130
+ {
131
+ start: 300128,
132
+ end: 399999,
133
+ prefix: "I",
134
+ aliases: [
135
+ "ilabs"
136
+ ]
137
+ },
138
+ {
139
+ start: 1e5,
140
+ end: 999999,
141
+ prefix: "V",
142
+ aliases: [
143
+ "mosaic"
144
+ ]
145
+ }
146
+ ];
147
+ const returningColumns = ["uid", "number", "lab", "totalAmount", "data", "date", "type", "famisInvoiceNumber", "customerAccount", "sellingAccount"];
148
+ const pad = (str, max, right) => {
149
+ let retVal = str;
150
+ str = str.toString();
151
+ if (str.length < max) {
152
+ retVal = pad("0" + str, max);
153
+ } else if (str.length > max) {
154
+ retVal = str.substring(0, max);
155
+ }
156
+ if (right) {
157
+ retVal = retVal.length < max ? pad(retVal + "0", max, right) : retVal;
158
+ }
159
+ return retVal;
160
+ };
161
+ const sanitizeAccountNumber = (account) => pad(account.replace(/\D/g, ""), 13, true);
162
+ const getCoreAccountNumber = (famis) => {
163
+ const { sellingCampusCode, sellingAccountNumber, sellingSupportAccount } = famis;
164
+ const account = `${sellingCampusCode}${sellingAccountNumber}${sellingSupportAccount}`;
165
+ return sanitizeAccountNumber(account);
166
+ };
167
+ const sortObjectKeys = (obj) => {
168
+ if (obj === null || obj === void 0) {
169
+ return obj;
170
+ }
171
+ if (typeof obj !== "object") {
172
+ return obj;
173
+ }
174
+ const result = Object.keys(obj).sort().reduce(
175
+ (acc, key) => {
176
+ if (Array.isArray(obj[key])) {
177
+ acc[key] = obj[key].map(sortObjectKeys);
178
+ } else if (typeof obj[key] === "object") {
179
+ acc[key] = sortObjectKeys(obj[key]);
180
+ } else {
181
+ acc[key] = obj[key];
182
+ }
183
+ return acc;
184
+ },
185
+ {}
186
+ );
187
+ return result;
188
+ };
189
+ class FAMISDb {
190
+ db;
191
+ //@ts-ignore
192
+ people;
193
+ constructor(config, people) {
194
+ if (config) {
195
+ this.init(config, people);
196
+ }
197
+ }
198
+ init(config, people) {
199
+ this.db = knex({
200
+ client: "pg",
201
+ connection: config,
202
+ debug: true,
203
+ pool: {
204
+ min: 0,
205
+ max: 10,
206
+ createTimeoutMillis: 3e4,
207
+ acquireTimeoutMillis: 3e4,
208
+ idleTimeoutMillis: 3e4,
209
+ reapIntervalMillis: 1e3,
210
+ createRetryIntervalMillis: 100
211
+ }
212
+ });
213
+ this.people = people;
214
+ }
215
+ /**
216
+ *
217
+ * Extract customer account and po number
218
+ * @param {object} famis
219
+ * @param {object} replace_data
220
+ * @returns {object} account number object
221
+ * @memberof FAMISDb
222
+ */
223
+ getAccountNumber(famis, replace_data) {
224
+ const retVal = {
225
+ accountNumber: "",
226
+ poNumber: ""
227
+ };
228
+ if (famis.customerAccountNumber || replace_data && replace_data.customerAccountNumber) {
229
+ retVal.accountNumber = replace_data && replace_data.customerAccountNumber ? replace_data.customerAccountNumber : famis.customerAccountNumber;
230
+ } else if (famis.customerPONumber.includes("|") || replace_data && replace_data.customerPONumber?.includes("|")) {
231
+ const result = replace_data && replace_data.customerPONumber?.includes("|") ? replace_data.customerPONumber?.split("|") : famis.customerPONumber.split("|");
232
+ retVal.accountNumber = sanitizeAccountNumber(result[0]);
233
+ retVal.poNumber = result[1].trim();
234
+ } else {
235
+ retVal.accountNumber = replace_data && replace_data.customerPONumber ? sanitizeAccountNumber(replace_data.customerPONumber) : sanitizeAccountNumber(famis.customerPONumber);
236
+ }
237
+ return retVal;
238
+ }
239
+ /**
240
+ *
241
+ * Process an invoice to have all necessary relationships
242
+ * @param {object} invoice - the invoice object sent by iLab
243
+ * @param {boolean} [historical=false] - if true use historical accounts too
244
+ * @returns {Promise<object>} invoice object
245
+ * @memberof FAMISDb
246
+ */
247
+ async processInvoice(invoice, historical = false) {
248
+ try {
249
+ const account = getCoreAccountNumber(invoice.data.details[0].famis);
250
+ invoice.core = await this.getCoreByAccount(account);
251
+ if (invoice.core) {
252
+ invoice.core.systemPart = await this.getSystemById(invoice.core.systemId);
253
+ } else {
254
+ const e = Error(`Revenue account ${account} is not associated with a core.`);
255
+ throw e;
256
+ }
257
+ const campus = account.substring(0, 2);
258
+ const revenueAccount = await this.getByAccountNumber(account, campus, historical);
259
+ const customerAccountNumber = this.getAccountNumber(invoice.data.details[0].famis, invoice.replace_data);
260
+ invoice.customerAccountInfo = await this.getByAccountNumber(customerAccountNumber.accountNumber, campus, historical);
261
+ if (invoice.customerAccountInfo) {
262
+ let idtAllowed = "idt_allowed" in invoice.customerAccountInfo && invoice.customerAccountInfo.idt_allowed !== null ? invoice.customerAccountInfo.idt_allowed : "parent_account_idt_allowed" in invoice.customerAccountInfo && invoice.customerAccountInfo.parent_account_idt_allowed;
263
+ idtAllowed = idtAllowed && !(invoice.replace_data && typeof invoice.replace_data === "object" && invoice.replace_data.forcedAr);
264
+ const isIdt = customerAccountNumber && customerAccountNumber.accountNumber?.substring(0, 2) === account.substring(0, 2) && idtAllowed;
265
+ invoice.isIdt = isIdt;
266
+ } else {
267
+ invoice.isIdt = false;
268
+ }
269
+ let poNumber = customerAccountNumber.accountNumber?.substring(0, 2) !== "68" ? `${campus}-${revenueAccount.department}` : customerAccountNumber.poNumber;
270
+ invoice.poNumber = poNumber;
271
+ invoice.customerAccount = customerAccountNumber.accountNumber;
272
+ if (invoice.customerAccountInfo && this.people) {
273
+ const pi = invoice.customerAccountInfo.uin ? await this.people.getByUin(invoice.customerAccountInfo.uin) : [];
274
+ if (pi.length) {
275
+ invoice.pi = JSON.parse(JSON.stringify(pi[0]));
276
+ }
277
+ }
278
+ invoice.highRiskInfo = await this.isSrsHighRisk(invoice);
279
+ invoice.comments = await this.getInvoiceComments(invoice.uid);
280
+ } catch (e) {
281
+ throw e;
282
+ }
283
+ return invoice;
284
+ }
285
+ /**
286
+ *
287
+ * Get invoice by uid
288
+ * @param {string} uid
289
+ * @param {string} [sortColumn='date']
290
+ * @param {string} [sortDirection='desc']
291
+ * @param {boolean} [deleted=false]
292
+ * @param {integer} page - page of results to return
293
+ * @param {string} perPage - number of results per page to return
294
+ * @returns {Promise<InvoicePayload>} invoice object
295
+ * @memberof FAMISDb
296
+ */
297
+ async getInvoiceByUid(uid, sortColumn = "date", sortDirection = "desc", page = 1, perPage = 50) {
298
+ let self = this;
299
+ let rows;
300
+ try {
301
+ rows = await this.db.from("invoices").andWhere("uid", uid).orderBy(sortColumn, sortDirection).select("*").paginate({ perPage, currentPage: page, isLengthAware: true });
302
+ rows.data = await Promise.all(
303
+ rows.data.map(async (row) => {
304
+ const rec = await self.processInvoice(row);
305
+ return rec;
306
+ })
307
+ );
308
+ } catch (e) {
309
+ throw e;
310
+ }
311
+ return rows;
312
+ }
313
+ /**
314
+ *
315
+ * Get invoice by originating system assigned number
316
+ * @param {string} number
317
+ * @param {string} [sortColumn='date']
318
+ * @param {string} [sortDirection='desc']
319
+ * @param {boolean} [deleted=false]
320
+ * @param {integer} page - page of results to return
321
+ * @param {string} perPage - number of results per page to return
322
+ * @returns {Promise<InvoicePayload>} invoice object
323
+ * @memberof FAMISDb
324
+ */
325
+ async getInvoiceByNumber(number, sortColumn = "date", sortDirection = "desc", page = 1, perPage = 50) {
326
+ let self = this;
327
+ let rows;
328
+ try {
329
+ rows = await this.db.from("invoices").andWhere("number", "ILIKE", `${number}%`).orderBy(sortColumn, sortDirection).select("*").paginate({ perPage, currentPage: page, isLengthAware: true });
330
+ rows.data = await Promise.all(
331
+ rows.data.map(async (row) => {
332
+ const rec = await self.processInvoice(row);
333
+ return rec;
334
+ })
335
+ );
336
+ } catch (e) {
337
+ throw e;
338
+ }
339
+ return rows;
340
+ }
341
+ /**
342
+ *
343
+ * Get invoice by fingerprint
344
+ * @param {string} fingerPrint
345
+ * @returns {Promise<object>} invoice object
346
+ * @memberof FAMISDb
347
+ */
348
+ async getInvoiceByFingerPrint(fingerPrint) {
349
+ let records;
350
+ let retVal;
351
+ try {
352
+ records = await this.db("invoices").where("fingerprint", fingerPrint);
353
+ if (records.length) {
354
+ retVal = await this.processInvoice(records[0]);
355
+ }
356
+ } catch (e) {
357
+ throw e;
358
+ }
359
+ return retVal;
360
+ }
361
+ /**
362
+ *
363
+ * Get all invoices associated with a specified account number
364
+ * @param {string} accountNumber
365
+ * @param {string} [sortColumn='date']
366
+ * @param {string} [sortDirection='desc']
367
+ * @param {boolean} [deleted=false]
368
+ * @param {integer} page - page of results to return
369
+ * @param {string} perPage - number of results per page to return
370
+ * @returns {Promise<InvoicePayload>} invoice objects
371
+ * @memberof FAMISDb
372
+ */
373
+ async getInvoicesByAccount(accountNumber, sortColumn = "date", sortDirection = "desc", page = 1, perPage = 50) {
374
+ let self = this;
375
+ let rows;
376
+ try {
377
+ const where = Array.isArray(accountNumber) ? `ALL(ARRAY[${accountNumber.map((n) => `'%${n}%'`).join()}])` : `ALL(ARRAY['%${accountNumber}%'])`;
378
+ rows = await this.db.from("invoices").whereNull("deleted").andWhere(function() {
379
+ this.where("customerAccount", "ilike", this.db.raw(where));
380
+ this.orWhere("sellingAccount", "ilike", this.db.raw(where));
381
+ this.orWhereRaw("replace_data->>'customerAccountNumber' = ?", [accountNumber]);
382
+ this.orWhereRaw("replace_data->>'customerPONumber' ILIKE ?", [`${accountNumber}%`]);
383
+ }).orderBy(sortColumn, sortDirection).select("*").paginate({ perPage, currentPage: page, isLengthAware: true });
384
+ rows.data = await Promise.all(
385
+ rows.data.map(async (row) => {
386
+ const rec = await self.processInvoice(row);
387
+ return rec;
388
+ })
389
+ );
390
+ } catch (e) {
391
+ throw e;
392
+ }
393
+ return rows;
394
+ }
395
+ /**
396
+ *
397
+ * Get all invoice ordered by and sorted by parameters passed
398
+ * @param {string} [sortColumn='date']
399
+ * @param {string} [sortDirection='desc']
400
+ * @param {boolean} [deleted=false]
401
+ * @param {integer} page - page of results to return
402
+ * @param {string} perPage - number of results per page to return
403
+ * @returns {Promise<InvoicePayload>} invoice objects
404
+ * @memberof FAMISDb
405
+ */
406
+ async getAllInvoices(sortColumn = "date", sortDirection = "desc", deleted, page = 1, perPage = 50) {
407
+ const self = this;
408
+ let rows;
409
+ try {
410
+ const qry = this.db.from("invoices").orderBy(sortColumn, sortDirection).select("*");
411
+ rows = deleted ? await qry.whereNotNull("deleted").paginate({ perPage, currentPage: page, isLengthAware: true }) : await qry.whereNull("deleted").paginate({ perPage, currentPage: page, isLengthAware: true });
412
+ rows.data = await Promise.all(
413
+ rows.data.map(async (row) => {
414
+ const rec = await self.processInvoice(row);
415
+ return rec;
416
+ })
417
+ );
418
+ } catch (e) {
419
+ throw e;
420
+ }
421
+ return rows;
422
+ }
423
+ /**
424
+ *
425
+ * Retrieves invoices from the latest date
426
+ * @param {string} [sortColumn='date']
427
+ * @param {string} [sortDirection='desc']
428
+ * @param {integer} first - corresponds to the first param of the Relay spec
429
+ * @param {string} after - corresponds to the after param of the Relay spec
430
+ * @param {integer} last - corresponds to the last param of the Relay spec
431
+ * @param {string} before - corresponds to the before param of the Relay spec
432
+ * @returns {Promise<InvoicePayload>} invoice objects
433
+ * @memberof FAMISDb
434
+ */
435
+ async getLatestInvoices(sortColumn = "date", sortDirection = "desc", page = 1, perPage = 50) {
436
+ const self = this;
437
+ let rows;
438
+ try {
439
+ rows = await this.db.from("invoices").whereNull("deleted").whereIn("date", function() {
440
+ this.max("date").from("invoices");
441
+ }).orderBy(sortColumn, sortDirection).select("*").paginate({ perPage, currentPage: page, isLengthAware: true });
442
+ rows.data = await Promise.all(
443
+ rows.data.map(async (row) => {
444
+ const rec = await self.processInvoice(row);
445
+ return rec;
446
+ })
447
+ );
448
+ } catch (e) {
449
+ throw e;
450
+ }
451
+ return rows;
452
+ }
453
+ /**
454
+ *
455
+ * Returns all held invoices
456
+ * @param {string} [sortColumn='date']
457
+ * @param {string} [sortDirection='desc']
458
+ * @param {integer} page - corresponds to the first param of the Relay spec
459
+ * @param {integer} perPage - corresponds to the last param of the Relay spec
460
+ * @returns {Promise<InvoicePayload>} held invoice objects
461
+ * @memberof FAMISDb
462
+ */
463
+ async getHeldInvoices(sortColumn = "date", sortDirection = "desc", page = 1, perPage = 50) {
464
+ const self = this;
465
+ let rows;
466
+ try {
467
+ rows = await this.db.from("invoices").whereNull("deleted").andWhere("invoiced", "0").orderBy(sortColumn, sortDirection).select("*").paginate({ perPage, currentPage: page, isLengthAware: true });
468
+ rows.data = await Promise.all(
469
+ rows.data.map(async (row) => {
470
+ const rec = await self.processInvoice(row);
471
+ return rec;
472
+ })
473
+ );
474
+ } catch (e) {
475
+ throw e;
476
+ }
477
+ return rows;
478
+ }
479
+ /**
480
+ *
481
+ * Returns all awaiting email invoices
482
+ * @param {string} [sortColumn='date']
483
+ * @param {string} [sortDirection='desc']
484
+ * @param {integer} first - corresponds to the first param of the Relay spec
485
+ * @param {string} after - corresponds to the after param of the Relay spec
486
+ * @param {integer} last - corresponds to the last param of the Relay spec
487
+ * @param {string} before - corresponds to the before param of the Relay spec
488
+ * @returns {Promise<InvoicePayload>} awaiting email invoice objects
489
+ * @memberof FAMISDb
490
+ */
491
+ async getInvoicesAwaitingEmail(sortColumn = "date", sortDirection = "desc", page = 1, perPage = 50) {
492
+ const self = this;
493
+ let rows;
494
+ try {
495
+ rows = await this.db.from("invoices").whereNull("deleted").andWhere("emailed", "False").orderBy(sortColumn, sortDirection).select("*").paginate({ perPage, currentPage: page, isLengthAware: true });
496
+ rows.data = await Promise.all(
497
+ rows.data.map(async (row) => {
498
+ const rec = await self.processInvoice(row);
499
+ return rec;
500
+ })
501
+ );
502
+ } catch (e) {
503
+ throw e;
504
+ }
505
+ return rows;
506
+ }
507
+ /**
508
+ *
509
+ * Search invoices by originating system generated invoice number
510
+ * @param {string} number
511
+ * @param {string} [sortColumn='date']
512
+ * @param {string} [sortDirection='desc']
513
+ * @param {integer} first - corresponds to the first param of the Relay spec
514
+ * @param {string} after - corresponds to the after param of the Relay spec
515
+ * @param {integer} last - corresponds to the last param of the Relay spec
516
+ * @param {string} before - corresponds to the before param of the Relay spec
517
+ * @returns {Promise<InvoicePayload>} invoice objects
518
+ * @memberof FAMISDb
519
+ */
520
+ async searchInvoicesByNumber(number, sortColumn = "date", sortDirection = "desc", page = 1, perPage = 50) {
521
+ const self = this;
522
+ let rows;
523
+ try {
524
+ rows = await this.db.from("invoices").whereNull("deleted").andWhere("number", "ilike", `${number}%`).orderBy(sortColumn, sortDirection).select("*").paginate({ perPage, currentPage: page, isLengthAware: true });
525
+ rows.data = await Promise.all(
526
+ rows.data.map(async (row) => {
527
+ const rec = await self.processInvoice(row);
528
+ return rec;
529
+ })
530
+ );
531
+ } catch (e) {
532
+ throw e;
533
+ }
534
+ return rows;
535
+ }
536
+ /**
537
+ *
538
+ * Search invoices by famis account number
539
+ * @param {string} account
540
+ * @param {string} [sortColumn='date']
541
+ * @param {string} [sortDirection='desc']
542
+ * @param {integer} first - corresponds to the first param of the Relay spec
543
+ * @param {string} after - corresponds to the after param of the Relay spec
544
+ * @param {integer} last - corresponds to the last param of the Relay spec
545
+ * @param {string} before - corresponds to the before param of the Relay spec
546
+ * @returns {Promise<InvoicePayload>} invoice objects
547
+ * @memberof FAMISDb
548
+ */
549
+ async searchInvoicesByAccountNumber(account, sortColumn = "date", sortDirection = "desc", page = 1, perPage = 50) {
550
+ const self = this;
551
+ let rows;
552
+ try {
553
+ rows = await this.db.from("invoices").whereNull("deleted").andWhere((q) => q.where("sellingAccount", "ilike", `${account}%`).orWhere("customerAccount", "ilike", `${account}%`)).orderBy(sortColumn, sortDirection).select("*").paginate({ perPage, currentPage: page, isLengthAware: true });
554
+ rows.data = await Promise.all(
555
+ rows.data.map(async (row) => {
556
+ const rec = await self.processInvoice(row);
557
+ return rec;
558
+ })
559
+ );
560
+ } catch (e) {
561
+ throw e;
562
+ }
563
+ return rows;
564
+ }
565
+ /**
566
+ *
567
+ * Search invoices by FAMIS invoice number
568
+ * @param {string} famis
569
+ * @param {string} [sortColumn='date']
570
+ * @param {string} [sortDirection='desc']
571
+ * @param {integer} page - page of results to return
572
+ * @param {integer} perPage - number of results per page to return
573
+ * @returns {Promise<InvoicePayload>} invoice objects
574
+ * @memberof FAMISDb
575
+ */
576
+ async searchInvoicesByFamis(famis, sortColumn = "date", sortDirection = "desc", page = 1, perPage = 50) {
577
+ const self = this;
578
+ let rows;
579
+ try {
580
+ rows = await this.db.from("invoices").whereNull("deleted").andWhere("famisInvoiceNumber", "ilike", `${famis}%`).orderBy(sortColumn, sortDirection).select("*").paginate({ perPage, currentPage: page, isLengthAware: true });
581
+ rows.data = await Promise.all(
582
+ rows.data.map(async (row) => {
583
+ const rec = await self.processInvoice(row);
584
+ return rec;
585
+ })
586
+ );
587
+ } catch (e) {
588
+ throw e;
589
+ }
590
+ return rows;
591
+ }
592
+ /**
593
+ *
594
+ * Queries invoices
595
+ * @param {string} query
596
+ * @param {string} [sortColumn='date']
597
+ * @param {string} [sortDirection='desc']
598
+ * @param {integer} page - page of results to return
599
+ * @param {integer} perPage - number of results per page to return
600
+ * @returns {Promise<InvoicePayload>} invoice objects
601
+ * @memberof FAMISDb
602
+ */
603
+ async queryInvoices(query, sortColumn = "date", sortDirection = "desc", page = 1, perPage = 50) {
604
+ const self = this;
605
+ let rows;
606
+ const invFields = invoiceFields.map((f) => `invoices.${f}`);
607
+ const expenseAccountFields = accountFields.map((f) => `expenseAccount.${f} AS expenseAccount_${f}`);
608
+ const revenueAccountFields = accountFields.map((f) => `revenueAccount.${f} AS revenueAccount_${f}`);
609
+ const expenseAccountDeptFields = departmentFields.map((f) => `expenseAccountDepartment.${f} AS expenseAccountDepartment_${f}`);
610
+ const revenueAccountDeptFields = departmentFields.map((f) => `revenueAccountDepartment.${f} AS revenueAccountDepartment_${f}`);
611
+ const expenseAccountBalFields = accountBalanceFields.map((f) => `expenseAccountBalance.${f} AS expenseAccountBalance_${f}`);
612
+ const fields = [
613
+ ...invFields,
614
+ ...expenseAccountFields,
615
+ ...revenueAccountFields,
616
+ ...expenseAccountDeptFields,
617
+ ...revenueAccountDeptFields,
618
+ ...expenseAccountBalFields
619
+ ];
620
+ try {
621
+ rows = await this.db.from("invoices").leftJoin("accounts as expenseAccount", "invoices.customerAccount", "expenseAccount.account_number").leftJoin("accounts as revenueAccount", "invoices.sellingAccount", "revenueAccount.account_number").leftJoin("departments as expenseAccountDepartment", function() {
622
+ this.on("expenseAccount.department", "=", "expenseAccountDepartment.dept").on("expenseAccountDepartment.part", "=", "expenseAccount.campus_code").andOnVal("expenseAccountDepartment.sub", null);
623
+ }).leftJoin("departments as revenueAccountDepartment", function() {
624
+ this.on("revenueAccount.department", "=", "revenueAccountDepartment.dept").on("revenueAccountDepartment.part", "=", "revenueAccount.campus_code");
625
+ }).leftJoin("account_balances as expenseAccountBalance", function() {
626
+ this.on("expenseAccountBalance.account_number", "=", "expenseAccount.budget_account_number").on("expenseAccountBalance.fiscal_year", "=", "expenseAccount.fiscal_year");
627
+ }).where(this.db.raw(query)).whereNull("revenueAccountDepartment.sub").orderBy(sortColumn, sortDirection).select(fields).paginate({ perPage, currentPage: page, isLengthAware: true });
628
+ rows.data = await Promise.all(
629
+ rows.data.map(async (row) => {
630
+ const rec = await self.processInvoice(row);
631
+ return rec;
632
+ })
633
+ );
634
+ } catch (e) {
635
+ throw e;
636
+ }
637
+ return rows;
638
+ }
639
+ /**
640
+ *
641
+ * Adds an invoice
642
+ * @param {object} invoice
643
+ * @param {string} type
644
+ * @param {string} famisInvoiceNumber
645
+ * @returns {Promise<object>} invoice
646
+ * @memberof FAMISDb
647
+ */
648
+ async addInvoice(invoice, type, famisInvoiceNumber) {
649
+ let records;
650
+ try {
651
+ famisInvoiceNumber = famisInvoiceNumber ?? void 0;
652
+ famisInvoiceNumber = famisInvoiceNumber ? famisInvoiceNumber : await this.getNextFAMISInvoiceNumber(type);
653
+ invoice.details.forEach((d) => {
654
+ d.famis.invoiceNumber = famisInvoiceNumber;
655
+ if (!d.other) {
656
+ d.other = "0.00";
657
+ }
658
+ if (!d.comments) {
659
+ d.comments = null;
660
+ }
661
+ if (!d.billingPeriod) {
662
+ d.billingPeriod = null;
663
+ }
664
+ });
665
+ records = await this.db("invoices").returning(returningColumns).insert({
666
+ number: invoice.number,
667
+ lab: invoice.lab,
668
+ totalAmount: parseFloat(invoice.totalAmount),
669
+ data: JSON.stringify(invoice),
670
+ date: invoice.date,
671
+ uid: cuid2.createId(),
672
+ fingerprint: invoice.fingerprint,
673
+ type,
674
+ famisInvoiceNumber,
675
+ customerAccount: this.getAccountNumber(invoice.details[0].famis).accountNumber,
676
+ sellingAccount: getCoreAccountNumber(invoice.details[0].famis)
677
+ });
678
+ } catch (e) {
679
+ throw e;
680
+ }
681
+ return records;
682
+ }
683
+ /**
684
+ *
685
+ * Updates an invoice
686
+ * @param {string} uid
687
+ * @param {object} invoice
688
+ * @returns {Promise<object>} invoice object
689
+ * @memberof FAMISDb
690
+ */
691
+ async updateInvoice(uid, invoice) {
692
+ let records;
693
+ invoice.details.forEach((d) => {
694
+ if (!d.other) {
695
+ d.other = "0.00";
696
+ }
697
+ if (!d.comments) {
698
+ d.comments = null;
699
+ }
700
+ if (!d.billingPeriod) {
701
+ d.billingPeriod = null;
702
+ }
703
+ });
704
+ try {
705
+ const record = {
706
+ number: invoice.number,
707
+ lab: invoice.lab,
708
+ totalAmount: parseFloat(invoice.totalAmount),
709
+ data: JSON.stringify(invoice),
710
+ date: invoice.date,
711
+ customerAccount: this.getAccountNumber(invoice.details[0].famis).accountNumber,
712
+ sellingAccount: getCoreAccountNumber(invoice.details[0].famis),
713
+ fingerprint: invoice.fingerprint ? invoice.fingerprint : null
714
+ };
715
+ if (invoice.replace_data) {
716
+ record.replace_data = invoice.replace_data;
717
+ }
718
+ records = await this.db("invoices").returning(returningColumns).update(record).where("uid", uid);
719
+ } catch (e) {
720
+ throw e;
721
+ }
722
+ return records;
723
+ }
724
+ /**
725
+ *
726
+ * Toggles an invoice as paid
727
+ * @param {string} uid
728
+ * @param {boolean} invoiced
729
+ * @returns {Promise<object>} invoice object
730
+ * @memberof FAMISDb
731
+ */
732
+ async markInvoiced(uid, invoiced) {
733
+ let records;
734
+ try {
735
+ records = await this.db("invoices").returning(returningColumns).update({
736
+ invoiced
737
+ }).where("uid", uid);
738
+ } catch (e) {
739
+ throw e;
740
+ }
741
+ return records;
742
+ }
743
+ /**
744
+ *
745
+ * Toggles an invoice as emailed
746
+ * @param {string} uid
747
+ * @param {boolean} emailed
748
+ * @returns {Promise<object>} invoice object
749
+ * @memberof FAMISDb
750
+ */
751
+ async markEmailed(uid, emailed) {
752
+ let records;
753
+ try {
754
+ records = await this.db("invoices").returning(returningColumns).update({
755
+ emailed
756
+ }).where("uid", uid);
757
+ } catch (e) {
758
+ throw e;
759
+ }
760
+ return records;
761
+ }
762
+ /**
763
+ *
764
+ * Sets an invoice's deletion date
765
+ * @param {string} uid
766
+ * @param {date} deleted
767
+ * @returns {Promise<object>} invoice object
768
+ * @memberof FAMISDb
769
+ */
770
+ async setDeleted(uid, deleted) {
771
+ let records;
772
+ try {
773
+ records = await this.db("invoices").returning(returningColumns).update({
774
+ deleted
775
+ }).where("uid", uid);
776
+ } catch (e) {
777
+ throw e;
778
+ }
779
+ return records;
780
+ }
781
+ /**
782
+ *
783
+ * Sets an invoice's posted date
784
+ * @param {string} uid
785
+ * @param {date} postedDate
786
+ * @returns {Promise<object>} invoice object
787
+ * @memberof FAMISDb
788
+ */
789
+ async setInvoicePostedDate(uid, postedDate) {
790
+ let records;
791
+ try {
792
+ records = await this.db("invoices").returning(returningColumns).update({
793
+ postedDate
794
+ }).where("uid", uid);
795
+ } catch (e) {
796
+ throw e;
797
+ }
798
+ return records;
799
+ }
800
+ /**
801
+ *
802
+ * Sets an invoice's fingerprint
803
+ * @param {string} uid
804
+ * @param {string} fingerprint
805
+ * @returns {Promise<object>} invoice object
806
+ * @memberof FAMISDb
807
+ */
808
+ async setInvoiceFingerPrint(uid, fingerprint) {
809
+ let records;
810
+ try {
811
+ records = await this.db("invoices").returning(returningColumns).update({
812
+ fingerprint
813
+ }).where("uid", uid);
814
+ } catch (e) {
815
+ throw e;
816
+ }
817
+ return records;
818
+ }
819
+ /**
820
+ *
821
+ * Get PI accounts by account number
822
+ * @param {string} number
823
+ * @param {string} campus
824
+ * @param {boolean} [historical=false]
825
+ * @returns {Promise<Account>} account object
826
+ * @memberof FAMISDb
827
+ */
828
+ async getByAccountNumber(number, campus = "02", historical = false) {
829
+ let records;
830
+ let retVal;
831
+ let table = historical ? "accounts_all" : "accounts";
832
+ const self = this;
833
+ if (!number) {
834
+ throw Error("Null account number");
835
+ }
836
+ try {
837
+ records = await this.db(`${table}`).leftJoin(
838
+ "departments",
839
+ function() {
840
+ this.on(`${table}.department`, "=", "departments.dept").on("departments.part", "=", self.db.raw("?", [number.substring(0, 2)]));
841
+ }
842
+ ).leftJoin(
843
+ `${table} as parent_account`,
844
+ function() {
845
+ this.on(`${table}.campus_code`, "=", "parent_account.campus_code").on(`${table}.account`, "=", "parent_account.account").on("parent_account.support_account", "=", self.db.raw("?", ["00000"])).on(`${table}.fiscal_year`, "=", "parent_account.fiscal_year");
846
+ }
847
+ ).select(
848
+ `${table}.*`,
849
+ "departments.idt_email as billingEmailId",
850
+ "parent_account.idt_allowed as parent_account_idt_allowed"
851
+ ).whereNull("departments.sub").andWhere(function() {
852
+ this.whereNull("departments.exp_date").orWhere("departments.exp_date", ">", self.db.raw("CURRENT_DATE"));
853
+ }).andWhere(`${table}.account_number`, number);
854
+ if (records.length) {
855
+ retVal = records[0];
856
+ } else {
857
+ records = await this.db("customers").where("account_number", number).andWhere("campus_code", campus);
858
+ if (records.length) {
859
+ retVal = Object.assign(
860
+ {},
861
+ records[0],
862
+ {
863
+ idt_allowed: false,
864
+ parent_account_idt_allowed: false,
865
+ billingEmailId: records[0].email
866
+ }
867
+ );
868
+ }
869
+ }
870
+ } catch (e) {
871
+ throw e;
872
+ }
873
+ return retVal;
874
+ }
875
+ /**
876
+ *
877
+ * Search PI accounts by partial account number
878
+ * @param {string|string[]} number
879
+ * @returns {Promise<AccountPayload>} account object(s)
880
+ * @memberof FAMISDb
881
+ */
882
+ async searchByAccountNumber(number, sortColumn = "account_number", sortDirection = "desc", page = 1, perPage = 50) {
883
+ let records;
884
+ const self = this;
885
+ try {
886
+ const where = Array.isArray(number) ? `ALL(ARRAY[${number.map((n) => `'%${n}%'`).join()}])` : `ALL(ARRAY['%${number}%'])`;
887
+ const accounts = await this.db("accounts").leftJoin("departments", function() {
888
+ this.on("accounts.department", "=", "departments.dept").on("departments.part", "=", "accounts.campus_code");
889
+ }).leftJoin("account_balances", function() {
890
+ this.on("account_balances.account_number", "=", "accounts.budget_account_number").on("account_balances.fiscal_year", "=", "accounts.fiscal_year");
891
+ }).select("accounts.*", "departments.idt_email as billingEmailId", "account_balances.balance").whereNull("departments.sub").andWhere(function() {
892
+ this.whereNull("departments.exp_date").orWhere("departments.exp_date", ">", self.db.raw("CURRENT_DATE"));
893
+ }).where("accounts.account_number", "ilike", this.db.raw(where)).unionAll([
894
+ this.db.select(
895
+ "NULL as uid",
896
+ "NULL as campus_code",
897
+ "NULL as account_number",
898
+ 'NULL as "name" as title',
899
+ "NULL as email",
900
+ "NULL as fiscal_year",
901
+ "NULL as account",
902
+ "NULL as support_account",
903
+ "NULL as budget_support_account",
904
+ "NULL as budget_account_number",
905
+ "NULL as expiration_date",
906
+ "NULL as status",
907
+ "NULL as uin",
908
+ "NULL as responsible_uin",
909
+ "NULL as department",
910
+ "NULL as setup_date",
911
+ "NULL as budget_access",
912
+ "NULL as abr_rule",
913
+ "NULL as idt_allowed",
914
+ "NULL as adloc_campus_code",
915
+ "NULL as adloc_department",
916
+ "NULL as adloc_name",
917
+ "NULL as preferred_first_name",
918
+ "NULL as first_name",
919
+ "NULL as middle_name",
920
+ "NULL as last_name",
921
+ "NULL as work_phone",
922
+ "NULL as active",
923
+ "NULL as default_bank",
924
+ "NULL as bank_override",
925
+ "NULL as last_seen",
926
+ "NULL as srs",
927
+ "NULL as cfda_no",
928
+ "NULL as sponsor_type",
929
+ "NULL as billingEmailId",
930
+ "NULL as balance"
931
+ ).from("customers").where("customers.account_number", "ilike", this.db.raw(where))
932
+ ]).orderBy(sortColumn, sortDirection).paginate({ perPage, currentPage: page, isLengthAware: true });
933
+ records = accounts;
934
+ } catch (e) {
935
+ throw e;
936
+ }
937
+ return records;
938
+ }
939
+ /**
940
+ *
941
+ * Get PI Accounts by UIN
942
+ * @param {string|string[]} uin
943
+ * @returns {Promise<Account[]>} account object array
944
+ * @memberof FAMISDb
945
+ */
946
+ async getAccountsByUin(uin, responsible, sortColumn = "account_number", sortDirection = "desc", page = 1, perPage = 50) {
947
+ let records;
948
+ const self = this;
949
+ try {
950
+ const where = Array.isArray(uin) ? `ALL(ARRAY[${uin.map((n) => `'%${n}%'`).join()}])` : `ALL(ARRAY['%${uin}%'])`;
951
+ const qry = this.db("accounts").join("departments", function() {
952
+ this.on("accounts.department", "=", "departments.dept").on("accounts.campus_code", "=", "departments.part");
953
+ }, "left").select("accounts.*", "departments.idt_email as billingEmailId").whereNull("departments.sub").andWhere(function() {
954
+ this.whereNull("departments.exp_date").orWhere("departments.exp_date", ">", self.db.raw("CURRENT_DATE"));
955
+ }).where("uin", "ilike", this.db.raw(where)).orderBy(sortColumn, sortDirection);
956
+ records = responsible ? await qry.orWhere("responsible_uin", "ilike", this.db.raw(where)).paginate({ perPage, currentPage: page, isLengthAware: true }) : await qry.paginate({ perPage, currentPage: page, isLengthAware: true });
957
+ } catch (e) {
958
+ throw e;
959
+ }
960
+ return records;
961
+ }
962
+ /**
963
+ *
964
+ * Get PI accounts by responsible UIN
965
+ * @param {string} uin
966
+ * @returns {Promise<Account[]>} account object array
967
+ * @memberof FAMISDb
968
+ */
969
+ async getAccountsByResponsibleUin(uin) {
970
+ let records;
971
+ const self = this;
972
+ try {
973
+ const where = Array.isArray(uin) ? `ALL(ARRAY[${uin.map((n) => `'%${n}%'`).join()}])` : `ALL(ARRAY['%${uin}%'])`;
974
+ records = await this.db("accounts").join("departments", function() {
975
+ this.on("accounts.department", "=", "departments.dept").on("accounts.campus_code", "=", "departments.part");
976
+ }, "left").select("accounts.*", "departments.idt_email as billingEmailId").whereNull("departments.sub").andWhere(function() {
977
+ this.whereNull("departments.exp_date").orWhere("departments.exp_date", ">", self.db.raw("CURRENT_DATE"));
978
+ }).where("uin", "ilike", this.db.raw(where));
979
+ } catch (e) {
980
+ throw e;
981
+ }
982
+ return records;
983
+ }
984
+ /**
985
+ *
986
+ * Queries accounts
987
+ * @param {string} query
988
+ * @param {string} [sortColumn='date']
989
+ * @param {string} [sortDirection='desc']
990
+ * @param {integer} page - page of results to return
991
+ * @param {integer} perPage - number of results per page to return
992
+ * @returns {Promise<AccountPayload>} invoice objects
993
+ * @memberof FAMISDb
994
+ */
995
+ async queryAccounts(query, sortColumn = "date", sortDirection = "desc", page = 1, perPage = 50) {
996
+ let rows;
997
+ const acctFields = accountFields.map((f) => `accounts.${f}`);
998
+ const accountBalFields = accountBalanceFields.map((f) => `accountBalance.${f} AS accountBalance_${f}`);
999
+ const deptFields = departmentFields.map((f) => `d.${f} AS department_${f}`);
1000
+ const accountantFields = personFields.map((f) => `accountant.${f} AS accountant_${f}`);
1001
+ const fields = [
1002
+ ...acctFields,
1003
+ ...accountBalFields,
1004
+ ...deptFields,
1005
+ ...accountantFields
1006
+ /*
1007
+ ...expenseAccountFields,
1008
+ ...revenueAccountFields,
1009
+ ...expenseAccountDeptFields,
1010
+ ...revenueAccountDeptFields,
1011
+ ...expenseAccountBalFields,
1012
+ */
1013
+ ];
1014
+ try {
1015
+ rows = await this.db.from("accounts").leftJoin("account_balances as accountBalance", function() {
1016
+ this.on("accountBalance.account_number", "=", "accounts.budget_account_number").on("accountBalance.fiscal_year", "=", "accounts.fiscal_year");
1017
+ }).leftJoin("departments as d", function() {
1018
+ this.on("accounts.department", "=", "d.dept").on("accounts.campus_code", "=", "d.part");
1019
+ }).leftJoin("people.current as accountant", function() {
1020
+ this.on("accounts.responsible_uin", "=", "accountant.tamuEduPersonUIN");
1021
+ }).where(this.db.raw(query)).whereNull("d.sub").orderBy(sortColumn, sortDirection).select(fields).paginate({ perPage, currentPage: page, isLengthAware: true });
1022
+ } catch (e) {
1023
+ throw e;
1024
+ }
1025
+ return rows;
1026
+ }
1027
+ /**
1028
+ *
1029
+ * Get next FAMIS invoice number in sequence for specified invoice type
1030
+ * @param {string} invoice
1031
+ * @returns {Promise<string>} next invoice number in sequence
1032
+ * @memberof FAMISDb
1033
+ */
1034
+ async getNextFAMISInvoiceNumber(type) {
1035
+ let records;
1036
+ let prefix = {
1037
+ start: 0,
1038
+ end: 0,
1039
+ prefix: "",
1040
+ aliases: []
1041
+ };
1042
+ let retVal;
1043
+ famisInvoiceNumbers.forEach((f) => {
1044
+ if (f.aliases.includes(type)) {
1045
+ prefix = f;
1046
+ }
1047
+ });
1048
+ try {
1049
+ retVal = `${prefix.prefix}${prefix.start}`;
1050
+ records = await this.db("invoices").whereIn("type", prefix.aliases).orderBy("famisInvoiceNumber", "desc");
1051
+ if (records.length) {
1052
+ const number = parseInt(records[0].famisInvoiceNumber.substring(1), 10) + 1;
1053
+ retVal = `${prefix.prefix}${number}`;
1054
+ }
1055
+ } catch (e) {
1056
+ throw e;
1057
+ }
1058
+ return retVal;
1059
+ }
1060
+ /**
1061
+ *
1062
+ * Get matching expense code for specified revenue code
1063
+ * @param {string} revenue
1064
+ * @returns {Promise<string>} expense code
1065
+ * @memberof FAMISDb
1066
+ */
1067
+ async getExpenseCodeByRevenue(revenue) {
1068
+ let records;
1069
+ let retVal;
1070
+ try {
1071
+ records = await this.db("codes").where("revenue", revenue).orderBy("id", "desc");
1072
+ if (records.length) {
1073
+ retVal = records[0].expense;
1074
+ }
1075
+ } catch (e) {
1076
+ throw e;
1077
+ }
1078
+ return retVal;
1079
+ }
1080
+ /**
1081
+ * Represents a row in the 'codes' table.
1082
+ * @typedef {Object} Code
1083
+ * @property {number} id - The unique identifier for the code.
1084
+ * @property {string|null} revenue - The revenue code (nullable).
1085
+ * @property {string|null} expense - The expense code (nullable).
1086
+ * @property {boolean} sales - Indicates if the code is associated with sales.
1087
+ * @property {boolean} srs - Indicates if the code is associated with SRS.
1088
+ */
1089
+ /**
1090
+ *
1091
+ * Get revenue code object
1092
+ * @param {string} revenue
1093
+ * @returns {Promise<Code>} revenue code object
1094
+ * @memberof FAMISDb
1095
+ */
1096
+ async getRevenueCode(revenue) {
1097
+ let records;
1098
+ let retVal;
1099
+ try {
1100
+ records = await this.db("codes").where("revenue", revenue).orderBy("id", "desc");
1101
+ if (records.length) {
1102
+ retVal = records[0];
1103
+ }
1104
+ } catch (e) {
1105
+ throw e;
1106
+ }
1107
+ return retVal;
1108
+ }
1109
+ /**
1110
+ *
1111
+ * Get expense code object
1112
+ * @param {string} code
1113
+ * @returns {Promise<Code>} expense code object
1114
+ * @memberof FAMISDb
1115
+ */
1116
+ async getExpenseCode(code) {
1117
+ let records;
1118
+ let retVal;
1119
+ try {
1120
+ records = await this.db("codes").where("expense", code).orderBy("id", "desc");
1121
+ if (records.length) {
1122
+ retVal = records[0];
1123
+ }
1124
+ } catch (e) {
1125
+ throw e;
1126
+ }
1127
+ return retVal;
1128
+ }
1129
+ /**
1130
+ *
1131
+ * Check if expense code is within the allowable range for specified account and the specified fiscal year (defaults to the current)
1132
+ * @param {string} accountNumber
1133
+ * @param {string} code
1134
+ * @param {integer} fiscal
1135
+ * @returns {Promise<boolean>} truthy value
1136
+ * @memberof FAMISDb
1137
+ */
1138
+ async allowableExpenseCode(accountNumber, code, fiscal) {
1139
+ let records;
1140
+ let retVal = false;
1141
+ const year = fiscal ? fiscal : this.getCurrentFiscalYear();
1142
+ try {
1143
+ records = await this.db("subcode_edits").select("allowed").where("campus_code", accountNumber.substring(0, 2)).andWhere("account", accountNumber.substring(2, 8)).andWhere("support_account", accountNumber.substring(8, 13)).andWhere("fiscal_year", year).andWhereRaw("? BETWEEN low AND high", [parseInt(code)]);
1144
+ if (records.length) {
1145
+ retVal = records[0].allowed;
1146
+ } else {
1147
+ records = await this.db("abr_rules").select("abr_rules.*").join("accounts", function() {
1148
+ this.on("accounts.abr_rule", "=", "abr_rules.abr_rule").on("accounts.campus_code", "=", "abr_rules.campus_code").on("accounts.fiscal_year", "=", "abr_rules.fiscal_year");
1149
+ }, "left").where("accounts.account_number", accountNumber).andWhere("accounts.fiscal_year", year).andWhereRaw("? BETWEEN abr_rules.low AND abr_rules.high", [parseInt(code)]);
1150
+ if (records.length) {
1151
+ retVal = true;
1152
+ }
1153
+ }
1154
+ } catch (e) {
1155
+ throw e;
1156
+ }
1157
+ return retVal;
1158
+ }
1159
+ /**
1160
+ *
1161
+ * Get allowable expense code ranges for the specified account
1162
+ * @param {string} accountNumber
1163
+ * @param {integer} fiscal
1164
+ * @returns {Promise<boolean>} expense code range object(s)
1165
+ * @memberof FAMISDb
1166
+ */
1167
+ async getAllowableExpenseCodes(accountNumber, fiscal) {
1168
+ let abr;
1169
+ let subcode;
1170
+ let disallowed;
1171
+ let retVal = {
1172
+ allowed: [],
1173
+ disallowed: []
1174
+ };
1175
+ const year = fiscal ? fiscal : this.getCurrentFiscalYear();
1176
+ try {
1177
+ subcode = await this.db("subcode_edits").select("low", "high").where("campus_code", accountNumber.substring(0, 2)).andWhere("account", accountNumber.substring(2, 8)).andWhere("support_account", accountNumber.substring(8, 13)).andWhere("fiscal_year", year).andWhere("allowed", true).orderBy("low", "asc");
1178
+ disallowed = await this.db("subcode_edits").select("low", "high").where("campus_code", accountNumber.substring(0, 2)).andWhere("account", accountNumber.substring(2, 8)).andWhere("support_account", accountNumber.substring(8, 13)).andWhere("fiscal_year", year).andWhere("allowed", false).orderBy("low", "asc");
1179
+ abr = await this.db("abr_rules").select("abr_rules.low", "abr_rules.high").join("accounts", function() {
1180
+ this.on("accounts.abr_rule", "=", "abr_rules.abr_rule").on("accounts.campus_code", "=", "abr_rules.campus_code").on("accounts.fiscal_year", "=", "abr_rules.fiscal_year");
1181
+ }, "left").where("accounts.account_number", accountNumber).andWhere("accounts.fiscal_year", year).orderBy("low", "asc");
1182
+ if (abr.length || subcode.length) {
1183
+ const allowed = [...abr, ...subcode];
1184
+ retVal = {
1185
+ allowed,
1186
+ disallowed
1187
+ };
1188
+ }
1189
+ } catch (e) {
1190
+ throw e;
1191
+ }
1192
+ return retVal;
1193
+ }
1194
+ /**
1195
+ *
1196
+ * Query cores
1197
+ * @param {string} query
1198
+ * @param {string} [sortColumn='facility']
1199
+ * @param {string} [sortDirection='asc']
1200
+ * @param {integer} page - page of results to return
1201
+ * @param {integer} perPage - number of results per page to return
1202
+ * @returns {Promise<CorePayload>} core objects
1203
+ * @memberof FAMISDb
1204
+ */
1205
+ async queryCores(query, sortColumn = "facility", sortDirection = "asc", page = 1, perPage = 50) {
1206
+ const self = this;
1207
+ let rows;
1208
+ const systemFields = systemPartFields.map((f) => `system.${f} AS system_${f}`);
1209
+ const fields = [
1210
+ ...systemFields
1211
+ /*
1212
+ ...expenseAccountFields,
1213
+ ...revenueAccountFields,
1214
+ ...expenseAccountDeptFields,
1215
+ ...revenueAccountDeptFields,
1216
+ ...expenseAccountBalFields,
1217
+ */
1218
+ ];
1219
+ try {
1220
+ rows = await this.db.with("coreaccounts", (qb) => {
1221
+ qb.select("accounts.*", "core_accounts.active", "core_accounts.coreId", "core_accounts.effective").from("core_accounts").join("accounts", "core_accounts.account", "accounts.account_number");
1222
+ }).with("coreusers", (qb) => {
1223
+ qb.select("users.*", "user_cores.email", "user_cores.coreId").from("user_cores").join("users", "user_cores.userId", "users.id");
1224
+ }).from("cores").leftJoin("system", "system.id", "cores.systemId").where(this.db.raw(query)).orderBy(sortColumn, sortDirection).leftJoin(
1225
+ this.db.from("user_cores").join("coreusers", { "user_cores.userId": "coreusers.id", "user_cores.coreId": "coreusers.coreId" }).select(
1226
+ "user_cores.coreId",
1227
+ this.db.raw(
1228
+ "case when count(coreusers) = 0 then '[]' else jsonb_agg(coreusers.*) end as users"
1229
+ )
1230
+ ).groupBy(["user_cores.coreId"]).as("user_cores"),
1231
+ "user_cores.coreId",
1232
+ "cores.id"
1233
+ ).leftJoin(
1234
+ this.db.from("core_accounts").join("coreaccounts", { "core_accounts.account": "coreaccounts.account_number", "core_accounts.coreId": "coreaccounts.coreId" }).select(
1235
+ "core_accounts.coreId",
1236
+ this.db.raw(
1237
+ "case when count(coreaccounts) = 0 then '[]' else jsonb_agg(coreaccounts.*) end as accounts"
1238
+ )
1239
+ ).groupBy(["core_accounts.coreId"]).as("core_accounts"),
1240
+ "core_accounts.coreId",
1241
+ "cores.id"
1242
+ ).select([
1243
+ "cores.*",
1244
+ "user_cores.users",
1245
+ "core_accounts.accounts",
1246
+ ...fields
1247
+ ]).paginate({ perPage, currentPage: page, isLengthAware: true });
1248
+ if (self.people) {
1249
+ rows.data = await Promise.all(
1250
+ rows.data.map(async (row) => {
1251
+ const uids = row.users?.map((u) => u.userId);
1252
+ if (uids) {
1253
+ const users = await self.people.bulkGetByUid(uids);
1254
+ if (users) {
1255
+ row.users = row.users?.map((u) => {
1256
+ let retVal = u;
1257
+ const user = users.find((u1) => u1.uid === u.userId);
1258
+ if (user) {
1259
+ retVal = {
1260
+ ...u,
1261
+ ...user
1262
+ };
1263
+ } else if (u.role) {
1264
+ retVal = {
1265
+ ...u,
1266
+ sn: u.roleName,
1267
+ mail: u.roleEmail
1268
+ };
1269
+ }
1270
+ return retVal;
1271
+ });
1272
+ }
1273
+ }
1274
+ return row;
1275
+ })
1276
+ );
1277
+ }
1278
+ } catch (e) {
1279
+ throw e;
1280
+ }
1281
+ return rows;
1282
+ }
1283
+ /**
1284
+ *
1285
+ * Get core by specified attribute and search term
1286
+ * @param {string} type
1287
+ * @param {string} operator
1288
+ * @param {string} search
1289
+ * @returns {Promise<Core>} core object
1290
+ * @memberof FAMISDb
1291
+ */
1292
+ async getCore(type, operator = "=", search) {
1293
+ let records;
1294
+ let retVal;
1295
+ try {
1296
+ const systemFields = systemPartFields.map((f) => `system.${f} AS system_${f}`);
1297
+ const fields = [
1298
+ ...systemFields
1299
+ /*
1300
+ ...expenseAccountFields,
1301
+ ...revenueAccountFields,
1302
+ ...expenseAccountDeptFields,
1303
+ ...revenueAccountDeptFields,
1304
+ ...expenseAccountBalFields,
1305
+ */
1306
+ ];
1307
+ records = await this.db.with("coreaccounts", (qb) => {
1308
+ qb.select("accounts.*", "core_accounts.coreId", "core_accounts.effective").from("core_accounts").join("accounts", "core_accounts.account", "accounts.account_number");
1309
+ }).with("coreusers", (qb) => {
1310
+ qb.select("users.*", "user_cores.email", "user_cores.coreId").from("user_cores").join("users", "user_cores.userId", "users.id");
1311
+ }).from("cores").leftJoin("system", "system.id", "cores.systemId").where(type, operator, search).leftJoin(
1312
+ this.db.from("user_cores").join("coreusers", { "user_cores.userId": "coreusers.id", "user_cores.coreId": "coreusers.coreId" }).select(
1313
+ "user_cores.coreId",
1314
+ this.db.raw(
1315
+ "case when count(coreusers) = 0 then '[]' else jsonb_agg(coreusers.*) end as users"
1316
+ )
1317
+ ).groupBy(["user_cores.coreId"]).as("user_cores"),
1318
+ "user_cores.coreId",
1319
+ "cores.id"
1320
+ ).leftJoin(
1321
+ this.db.from("core_accounts").join("coreaccounts", { "core_accounts.account": "coreaccounts.account_number", "core_accounts.coreId": "coreaccounts.coreId" }).select(
1322
+ "core_accounts.coreId",
1323
+ this.db.raw(
1324
+ "case when count(coreaccounts) = 0 then '[]' else jsonb_agg(coreaccounts.*) end as accounts"
1325
+ )
1326
+ ).groupBy(["core_accounts.coreId"]).as("core_accounts"),
1327
+ "core_accounts.coreId",
1328
+ "cores.id"
1329
+ ).select([
1330
+ "cores.*",
1331
+ "user_cores.users",
1332
+ "core_accounts.accounts",
1333
+ ...fields
1334
+ ]);
1335
+ if (records.length) {
1336
+ retVal = records[0];
1337
+ if (this.people) {
1338
+ const uids = retVal.users?.map((u) => u.userId);
1339
+ if (uids) {
1340
+ const users = await this.people.bulkGetByUid(uids);
1341
+ if (users) {
1342
+ retVal.users = retVal.users?.map((u) => {
1343
+ let retVal2 = u;
1344
+ const user = users.find((u1) => u1.uid === u.userId);
1345
+ if (user) {
1346
+ retVal2 = {
1347
+ ...u,
1348
+ ...user
1349
+ };
1350
+ } else if (u.role) {
1351
+ retVal2 = {
1352
+ ...u,
1353
+ sn: u.roleName,
1354
+ mail: u.roleEmail
1355
+ };
1356
+ }
1357
+ return retVal2;
1358
+ });
1359
+ }
1360
+ }
1361
+ }
1362
+ }
1363
+ } catch (e) {
1364
+ throw e;
1365
+ }
1366
+ return retVal;
1367
+ }
1368
+ /**
1369
+ *
1370
+ * Get core by id
1371
+ * @param {string} id
1372
+ * @returns {Promise<Core>} core object
1373
+ * @memberof FAMISDb
1374
+ */
1375
+ async getCoreById(id) {
1376
+ let retVal;
1377
+ try {
1378
+ retVal = await this.getCore("cores.id", "=", id);
1379
+ } catch (e) {
1380
+ throw e;
1381
+ }
1382
+ return retVal;
1383
+ }
1384
+ /**
1385
+ *
1386
+ * Get core by account number
1387
+ * @param {string} account
1388
+ * @returns {Promise<Core>} core object
1389
+ * @memberof FAMISDb
1390
+ */
1391
+ async getCoreByAccount(account) {
1392
+ let retVal;
1393
+ try {
1394
+ retVal = await this.getCore("core_accounts.accounts", "@>", `[{"account_number": "${account}"}]`);
1395
+ } catch (e) {
1396
+ throw e;
1397
+ }
1398
+ return retVal;
1399
+ }
1400
+ /**
1401
+ *
1402
+ * Update core
1403
+ * @param {Core} core object
1404
+ * @returns {Promise<Core>} core object
1405
+ * @memberof FAMISDb
1406
+ */
1407
+ async updateCore(core) {
1408
+ let retVal;
1409
+ const columns = ["id", "account", "effective", "facility", "contact", "contactEmail", "systemId", "enabled", "deleted", "coreEmail", "corePhone", "coreAddress", "from_email", "invoice_email"];
1410
+ try {
1411
+ if ("id" in core) {
1412
+ retVal = await this.db("cores").returning(columns).update(core).where("id", core.id);
1413
+ const systemPart = this.getSystemById(retVal.systemId);
1414
+ retVal.systemPart = systemPart;
1415
+ }
1416
+ } catch (e) {
1417
+ throw e;
1418
+ }
1419
+ return retVal;
1420
+ }
1421
+ /**
1422
+ *
1423
+ * Add core
1424
+ * @param {Core} core object
1425
+ * @returns {Promise<Core>} core object
1426
+ * @memberof FAMISDb
1427
+ */
1428
+ async addCore(core) {
1429
+ let retVal;
1430
+ const columns = ["id", "account", "effective", "facility", "contact", "contactEmail", "systemId", "enabled", "deleted", "coreEmail", "corePhone", "coreAddress", "from_email", "invoice_email"];
1431
+ try {
1432
+ retVal = await this.db("cores").returning(columns).insert(core);
1433
+ const systemPart = this.getSystemById(retVal.systemId);
1434
+ retVal.systemPart = systemPart;
1435
+ } catch (e) {
1436
+ throw e;
1437
+ }
1438
+ return retVal;
1439
+ }
1440
+ /**
1441
+ *
1442
+ * Update core account
1443
+ * @param {CoreAccount} account object
1444
+ * @returns {Promise<CoreAccount>} core_account object
1445
+ * @memberof FAMISDb
1446
+ */
1447
+ async updateCoreAccount(account) {
1448
+ let retVal;
1449
+ const columns = ["id", "account", "effective", "coreId", "active"];
1450
+ try {
1451
+ if ("id" in account) {
1452
+ retVal = await this.db("core_accounts").returning(columns).update(account).where("id", account.id);
1453
+ }
1454
+ } catch (e) {
1455
+ throw e;
1456
+ }
1457
+ return retVal;
1458
+ }
1459
+ /**
1460
+ *
1461
+ * Add core_account
1462
+ * @param {CoreAccount} account object
1463
+ * @returns {Promise<CoreAccount>} core_account object
1464
+ * @memberof FAMISDb
1465
+ */
1466
+ async addCoreAccount(account) {
1467
+ let retVal;
1468
+ const columns = ["id", "account", "effective", "coreId", "active"];
1469
+ try {
1470
+ retVal = await this.db("core_accounts").returning(columns).insert(account);
1471
+ } catch (e) {
1472
+ throw e;
1473
+ }
1474
+ return retVal;
1475
+ }
1476
+ /**
1477
+ *
1478
+ * Get system part by ID
1479
+ * @param {number} id
1480
+ * @returns {Promise<SystemPart>} system part object
1481
+ * @memberof FAMISDb
1482
+ */
1483
+ async getSystemById(id) {
1484
+ let records;
1485
+ let retVal;
1486
+ try {
1487
+ records = await this.db("system").where("id", id).orderBy("id", "desc");
1488
+ if (records.length) {
1489
+ retVal = records[0];
1490
+ }
1491
+ } catch (e) {
1492
+ throw e;
1493
+ }
1494
+ return retVal;
1495
+ }
1496
+ /**
1497
+ *
1498
+ * Get system part by two-digit part number
1499
+ * @param {string} number
1500
+ * @returns {Promise<SystemPart>} system part object
1501
+ * @memberof FAMISDb
1502
+ */
1503
+ async getSystemByPartNumber(number) {
1504
+ let records;
1505
+ let retVal;
1506
+ try {
1507
+ records = await this.db("system").where("part", number).orderBy("id", "desc");
1508
+ if (records.length) {
1509
+ retVal = records[0];
1510
+ }
1511
+ } catch (e) {
1512
+ throw e;
1513
+ }
1514
+ return retVal;
1515
+ }
1516
+ /**
1517
+ *
1518
+ * Get all enabled cores
1519
+ * @returns {Promise<Core[]>} core objects
1520
+ * @memberof FAMISDb
1521
+ */
1522
+ async getEnabledCores() {
1523
+ let records;
1524
+ let retVal = [];
1525
+ try {
1526
+ records = await this.db("cores").select(
1527
+ "cores.id",
1528
+ "core_accounts.account",
1529
+ "core_accounts.effective",
1530
+ "core_accounts.active",
1531
+ "cores.facility",
1532
+ "cores.contact",
1533
+ "cores.contactEmail",
1534
+ "cores.systemId",
1535
+ "cores.enabled",
1536
+ "cores.deleted",
1537
+ "cores.coreEmail",
1538
+ "cores.corePhone",
1539
+ "cores.coreAddress",
1540
+ "cores.from_email",
1541
+ "cores.invoice_email"
1542
+ ).join("core_accounts", "cores.id", "=", "core_accounts.coreId").where("enabled", "1").andWhere("core_accounts.active", "True").groupBy("cores.id").orderBy("cores.id", "desc");
1543
+ if (records.length) {
1544
+ retVal = records;
1545
+ }
1546
+ } catch (e) {
1547
+ throw e;
1548
+ }
1549
+ return retVal;
1550
+ }
1551
+ /**
1552
+ *
1553
+ * Get all cores
1554
+ * @param {string} [sortColumn='facility']
1555
+ * @param {string} [sortDirection='asc']
1556
+ * @param {boolean} [deleted=false]
1557
+ * @param {integer} page - page of results to return
1558
+ * @param {string} perPage - number of results per page to return
1559
+ * @returns {Promise<CorePayload>} core objects
1560
+ * @memberof FAMISDb
1561
+ */
1562
+ async getAllCores(sortColumn = "facility", sortDirection = "asc", deleted, page = 1, perPage = 50) {
1563
+ let rows;
1564
+ try {
1565
+ const systemParts = await this.getAllSystemParts();
1566
+ const qry = this.db("cores").orderBy(sortColumn, sortDirection).select("*");
1567
+ rows = deleted ? await qry.whereNotNull("deleted").paginate({ perPage, currentPage: page, isLengthAware: true }) : await qry.whereNull("deleted").paginate({ perPage, currentPage: page, isLengthAware: true });
1568
+ rows.data = rows.data.map((c) => {
1569
+ const systemPart = systemParts.find((s) => s.id === c.systemId);
1570
+ return {
1571
+ ...c,
1572
+ systemPart
1573
+ };
1574
+ });
1575
+ } catch (e) {
1576
+ throw e;
1577
+ }
1578
+ return rows;
1579
+ }
1580
+ /**
1581
+ *
1582
+ * Get a core's users by core ID
1583
+ * @param {number} coreId
1584
+ * @returns {Promise<Core[]>} user objects
1585
+ * @memberof FAMISDb
1586
+ */
1587
+ async getCoreUsers(coreId) {
1588
+ let retVal;
1589
+ try {
1590
+ retVal = await this.db("user_cores").join("users", "user_cores.userId", "=", "users.id").where("coreId", coreId).select(
1591
+ "users.*",
1592
+ "user_cores.id as userCoreId",
1593
+ "user_cores.coreId",
1594
+ "user_cores.email"
1595
+ );
1596
+ } catch (e) {
1597
+ throw e;
1598
+ }
1599
+ return retVal;
1600
+ }
1601
+ /**
1602
+ *
1603
+ * Update core user
1604
+ * @param {CoreUser} user object
1605
+ * @returns {Promise<CoreUser>} user_core object
1606
+ * @memberof FAMISDb
1607
+ */
1608
+ async updateCoreUser(user) {
1609
+ let retVal;
1610
+ const columns = ["id", "userId", "coreId", "email"];
1611
+ try {
1612
+ if ("id" in user) {
1613
+ retVal = await this.db("user_cores").returning(columns).update(user).where("id", user.id);
1614
+ }
1615
+ } catch (e) {
1616
+ throw e;
1617
+ }
1618
+ return retVal;
1619
+ }
1620
+ /**
1621
+ *
1622
+ * Add core user
1623
+ * @param {CoreUser} coreUser core user object
1624
+ * @param {User} user user object
1625
+ * @returns {Promise<CoreUser>} core user object
1626
+ * @memberof FAMISDb
1627
+ */
1628
+ async addCoreUser(coreUser, user) {
1629
+ let retVal;
1630
+ const columns = ["id", "userId", "coreId", "email"];
1631
+ try {
1632
+ let record = {
1633
+ userId: coreUser.userId
1634
+ };
1635
+ const [userInsert] = await this.db("users").insert(record).onConflict("userId").merge().returning("*");
1636
+ if (userInsert) {
1637
+ retVal = await this.db("user_cores").insert({
1638
+ ...coreUser,
1639
+ userId: userInsert.id
1640
+ }).onConflict(["userId", "coreId"]).ignore().returning(columns);
1641
+ }
1642
+ } catch (e) {
1643
+ throw e;
1644
+ }
1645
+ return retVal;
1646
+ }
1647
+ /**
1648
+ *
1649
+ * Get a user's cores they manage
1650
+ * @param {string} userId
1651
+ * @param {string} [sortColumn='facility']
1652
+ * @param {string} [sortDirection='asc']
1653
+ * @param {boolean} [deleted=false]
1654
+ * @param {integer} page - page of results to return
1655
+ * @param {string} perPage - number of results per page to return
1656
+ * @returns {Promise<CorePayload]>} core objects
1657
+ * @memberof FAMISDb
1658
+ */
1659
+ async getUserCores(userId, sortColumn = "facility", sortDirection = "asc", deleted, page = 1, perPage = 50) {
1660
+ let rows;
1661
+ try {
1662
+ const systemParts = await this.getAllSystemParts();
1663
+ const qry = this.db("user_cores").join("cores", "user_cores.coreId", "=", "cores.id").join("users", "user_cores.userId", "=", "users.id").where("users.userId", userId).select(
1664
+ "cores.*",
1665
+ "user_cores.id as userCoreId",
1666
+ "user_cores.userId",
1667
+ "user_cores.email"
1668
+ ).orderBy(sortColumn, sortDirection);
1669
+ rows = deleted ? await qry.whereNotNull("cores.deleted").paginate({ perPage, currentPage: page, isLengthAware: true }) : await qry.whereNull("cores.deleted").paginate({ perPage, currentPage: page, isLengthAware: true });
1670
+ rows.data = rows.data.map((c) => {
1671
+ const systemPart = systemParts.find((s) => s.id === c.systemId);
1672
+ return {
1673
+ ...c,
1674
+ systemPart
1675
+ };
1676
+ });
1677
+ } catch (e) {
1678
+ throw e;
1679
+ }
1680
+ return rows;
1681
+ }
1682
+ /**
1683
+ *
1684
+ * Get a system part's users by system ID
1685
+ * @param {number} systemId
1686
+ * @returns {Promise<object[]>} user objects
1687
+ * @memberof FAMISDb
1688
+ */
1689
+ async getSystemUsers(systemId) {
1690
+ let retVal;
1691
+ try {
1692
+ retVal = await this.db("user_systems").join("users", "user_systems.userId", "=", "users.id").where("systemId", systemId).select(
1693
+ "users.*",
1694
+ "user_systems.id as userSystemId",
1695
+ "user_systems.systemId",
1696
+ "user_systems.email"
1697
+ );
1698
+ } catch (e) {
1699
+ throw e;
1700
+ }
1701
+ return retVal;
1702
+ }
1703
+ /**
1704
+ *
1705
+ * Get a user's system parts they manage
1706
+ * @param {string} userId
1707
+ * @returns {Promise<SystemPart[]>} system objects
1708
+ * @memberof FAMISDb
1709
+ */
1710
+ async getUserSystemParts(userId) {
1711
+ let retVal;
1712
+ try {
1713
+ retVal = await this.db("user_systems").join("system", "user_systems.systemId", "=", "system.id").join("users", "user_systems.userId", "=", "users.id").where("users.userId", userId).select(
1714
+ "system.*",
1715
+ "user_systems.id as userSystemId",
1716
+ "user_systems.userId",
1717
+ "user_systems.email"
1718
+ );
1719
+ } catch (e) {
1720
+ throw e;
1721
+ }
1722
+ return retVal;
1723
+ }
1724
+ /**
1725
+ * Represents the "system" table in the public schema.
1726
+ * @typedef {Object} System
1727
+ * @property {number} id - The ID (auto-generated).
1728
+ * @property {string | null} name - The name.
1729
+ * @property {string | null} due - The due text.
1730
+ * @property {string | null} fein - The FEIN (Federal Employer Identification Number).
1731
+ * @property {string | null} agency - The agency.
1732
+ * @property {string | null} checkAddress - The check address.
1733
+ * @property {boolean} credit - Indicates if credit is allowed.
1734
+ * @property {string | null} creditUrl - The credit URL.
1735
+ * @property {string | null} creditFee - The credit fee.
1736
+ * @property {string | null} part - The part.
1737
+ * @property {string | null} abbrev - The abbreviation.
1738
+ * @property {string | null} aggiebuy - The AggieBuy information.
1739
+ * @property {string | null} image - The image URL.
1740
+ */
1741
+ /**
1742
+ * Get all system parts.
1743
+ * @returns {Promise<SystemPart[]>} System objects.
1744
+ * @memberof FAMISDb
1745
+ */
1746
+ async getAllSystemParts() {
1747
+ let retVal;
1748
+ try {
1749
+ retVal = await this.db("system").orderBy("id", "asc");
1750
+ } catch (e) {
1751
+ throw e;
1752
+ }
1753
+ return retVal;
1754
+ }
1755
+ /**
1756
+ * Get all admin users.
1757
+ * @returns {Promise<object[]>} Array of user objects.
1758
+ * @memberof FAMISDb
1759
+ */
1760
+ async getAdminUsers() {
1761
+ let retVal = [];
1762
+ try {
1763
+ retVal = await this.db("users").where("admin", true);
1764
+ } catch (e) {
1765
+ throw e;
1766
+ }
1767
+ return retVal;
1768
+ }
1769
+ /**
1770
+ * Get user.
1771
+ * @param {string} userId - User ID.
1772
+ * @returns {Promise<object>} User object.
1773
+ * @memberof FAMISDb
1774
+ */
1775
+ async getUser(userId) {
1776
+ let records;
1777
+ let retVal = null;
1778
+ try {
1779
+ records = await this.db("users").where("userId", userId);
1780
+ if (records.length) {
1781
+ const cores = await this.getUserCores(userId);
1782
+ const systems = await this.getUserSystemParts(userId);
1783
+ retVal = {
1784
+ ...records[0],
1785
+ cores,
1786
+ systems
1787
+ };
1788
+ }
1789
+ } catch (e) {
1790
+ throw e;
1791
+ }
1792
+ return retVal;
1793
+ }
1794
+ /**
1795
+ * Update user.
1796
+ * @param {object} user - User object.
1797
+ * @returns {Promise<object>} Updated user object.
1798
+ * @memberof FAMISDb
1799
+ */
1800
+ async updateUser(user) {
1801
+ let retVal = null;
1802
+ const columns = ["id", "userId", "admin", "role", "roleName", "roleEmail", "deleted"];
1803
+ try {
1804
+ if ("id" in user)
1805
+ retVal = await this.db("users").returning(columns).update(user).where("id", user.id);
1806
+ } catch (e) {
1807
+ throw e;
1808
+ }
1809
+ return retVal;
1810
+ }
1811
+ /**
1812
+ * Get customer by customer account number.
1813
+ * @param {string} accountNumber - Customer account number.
1814
+ * @param {string} campusCode - Campus code.
1815
+ * @returns {Promise<object>} Customer account object.
1816
+ * @memberof FAMISDb
1817
+ */
1818
+ async getCustomerNumber(accountNumber, campusCode) {
1819
+ let records;
1820
+ let retVal = null;
1821
+ const code = pad(campusCode, 2);
1822
+ try {
1823
+ records = await this.db("customers").where("uid", `${code}${accountNumber}`);
1824
+ if (records.length) {
1825
+ retVal = records[0];
1826
+ }
1827
+ } catch (e) {
1828
+ throw e;
1829
+ }
1830
+ return retVal;
1831
+ }
1832
+ /**
1833
+ * Get current fiscal year.
1834
+ * @returns {number} Fiscal year.
1835
+ * @memberof FAMISDb
1836
+ */
1837
+ getCurrentFiscalYear() {
1838
+ let year;
1839
+ let today = /* @__PURE__ */ new Date();
1840
+ if (today.getMonth() + 1 >= 9) {
1841
+ year = today.getFullYear() + 1;
1842
+ } else {
1843
+ year = today.getFullYear();
1844
+ }
1845
+ return year;
1846
+ }
1847
+ /**
1848
+ * Return fingerprint of invoice.
1849
+ * @param {object} invoice - JSON object.
1850
+ * @returns {string} Fingerprint.
1851
+ * @memberof FAMISDb
1852
+ */
1853
+ getInvoiceFingerPrint(invoice) {
1854
+ const fingerPrint = revisionHash(
1855
+ JSON.stringify(
1856
+ sortObjectKeys(invoice),
1857
+ (_k, v) => v
1858
+ )
1859
+ );
1860
+ return fingerPrint;
1861
+ }
1862
+ /**
1863
+ * Return formatted account number.
1864
+ * @param {string} accountNumber - Unformatted 13-digit FAMIS account number.
1865
+ * @returns {string} Formatted 13-digit FAMIS account number.
1866
+ * @memberof FAMISDb
1867
+ */
1868
+ formatAccountNumber(accountNumber) {
1869
+ const formatted = accountNumber.replace(/(\d{2})(\d{6})(\d{5})/, "$1-$2-$3");
1870
+ return formatted;
1871
+ }
1872
+ /**
1873
+ * Is invoice flagged by SRS high-risk policies.
1874
+ * @param {object} invoice - JSON object.
1875
+ * @returns {Promise<HighRisk>} High-risk object.
1876
+ * @memberof FAMISDb
1877
+ */
1878
+ async isSrsHighRisk(invoice) {
1879
+ let srsHighRisk = false;
1880
+ const objectCodes = /* @__PURE__ */ new Map();
1881
+ try {
1882
+ await awaity.each(
1883
+ invoice.data.details,
1884
+ async (detail) => {
1885
+ if (detail.famis.customerAccountNumber && detail.famis.sellingCampusCode) {
1886
+ const acct = await this.getByAccountNumber(detail.famis.customerAccountNumber, detail.famis.sellingCampusCode);
1887
+ const objCode = await this.getRevenueCode(detail.sellingObjectCode);
1888
+ const isFederal = acct && acct.cfda_no && (acct.sponsor_type && acct.sponsor_type === "F");
1889
+ const srsApproved = invoice.replace_data && "srs_approved" in invoice.replace_data ? invoice.replace_data.srs_approved : false;
1890
+ if (objCode && objCode.srs && acct && acct.srs && isFederal && !srsApproved && invoice.isIdt) {
1891
+ srsHighRisk = true;
1892
+ let val = [detail];
1893
+ if (objectCodes.has(objCode.expense))
1894
+ val.push(...objectCodes.get(objCode.expense));
1895
+ objectCodes.set(objCode.expense, val);
1896
+ }
1897
+ }
1898
+ }
1899
+ );
1900
+ } catch (e) {
1901
+ console.log(e);
1902
+ }
1903
+ return {
1904
+ isHighRisk: srsHighRisk,
1905
+ flaggedTransactions: Object.fromEntries(objectCodes)
1906
+ };
1907
+ }
1908
+ /**
1909
+ * @typedef {Object} Comment
1910
+ * @property {integer} id - db generated id
1911
+ * @property {string} uid - uid
1912
+ * @property {string} invoice_id - invoice id
1913
+ * @property {string} user_id - user uid
1914
+ * @property {string} comment - text of comment
1915
+ * @property {string} type - type of comment
1916
+ * @property {date} created - created date
1917
+ * @property {date} deleted - deleted date
1918
+ */
1919
+ /**
1920
+ * Create or update an invoice comment.
1921
+ * @param {Comment} comment - JSON object representing the comment
1922
+ * @returns {Promise<Comment>} - Promise that resolves to the comment object
1923
+ * @memberof FAMISDb
1924
+ */
1925
+ async saveComment(comment) {
1926
+ const returningColumns2 = [
1927
+ "id",
1928
+ "uid",
1929
+ "invoice_id",
1930
+ "user_id",
1931
+ "comment",
1932
+ "type",
1933
+ "created",
1934
+ "deleted"
1935
+ ];
1936
+ let response;
1937
+ try {
1938
+ const records = await this.db("invoice_comments").where({ uid: comment.uid });
1939
+ if (records.length) {
1940
+ response = await this.db("invoice_comments").returning(returningColumns2).update(comment).where("uid", comment.uid);
1941
+ } else {
1942
+ response = await this.db("invoice_comments").returning(returningColumns2).insert(comment);
1943
+ }
1944
+ if (response && Array.isArray(response) && response.length) {
1945
+ response = response[0];
1946
+ }
1947
+ } catch (e) {
1948
+ console.log(e);
1949
+ throw e;
1950
+ }
1951
+ return response;
1952
+ }
1953
+ /**
1954
+ * Get comments associated with an invoice.
1955
+ * @param {string} invoiceUid - Invoice UID
1956
+ * @returns {Promise<Comment[]>} - Promise that resolves to an array of comment objects
1957
+ * @memberof FAMISDb
1958
+ */
1959
+ async getInvoiceComments(invoiceUid) {
1960
+ let self = this;
1961
+ let response;
1962
+ try {
1963
+ const records = await this.db("invoice_comments").select(
1964
+ "invoice_comments.*"
1965
+ ).where({ invoice_id: invoiceUid }).orderBy("created", "desc");
1966
+ response = await awaity.map(
1967
+ records,
1968
+ async (r) => {
1969
+ const retVal = r;
1970
+ const files = await self.getCommentFiles(r.uid);
1971
+ r.files = files;
1972
+ if (self.people) {
1973
+ const user = await self.people.getByUid(r.user_id);
1974
+ if (user) {
1975
+ retVal.user = user;
1976
+ }
1977
+ }
1978
+ return retVal;
1979
+ }
1980
+ );
1981
+ } catch (e) {
1982
+ console.log(e);
1983
+ throw e;
1984
+ }
1985
+ return response;
1986
+ }
1987
+ /**
1988
+ * @typedef {Object} CommentFile
1989
+ * @property {integer} id - db generated id
1990
+ * @property {string} uid - uid
1991
+ * @property {string} invoice_id - invoice uid
1992
+ * @property {string} comment_id - comment uid
1993
+ * @property {string} original_filename - original file name
1994
+ * @property {string} filename - generate unique filename on filesystem
1995
+ * @property {date} created - created date
1996
+ * @property {date} deleted - deleted date
1997
+ */
1998
+ /**
1999
+ * Get invoice comment files.
2000
+ * @param {string} commentUid - Comment UID
2001
+ * @returns {Promise<CommentFile[]>} - Promise that resolves to an array of comment file objects
2002
+ * @memberof FAMISDb
2003
+ */
2004
+ async getCommentFiles(commentUid) {
2005
+ let response = [];
2006
+ try {
2007
+ const records = await this.db("invoice_comment_files").where({ comment_id: commentUid }).orderBy("created", "desc");
2008
+ response = records;
2009
+ } catch (e) {
2010
+ console.log(e);
2011
+ throw e;
2012
+ }
2013
+ return response;
2014
+ }
2015
+ /**
2016
+ * Get invoice comment file.
2017
+ * @param {string} fileUid - File UID
2018
+ * @returns {Promise<CommentFile>} - Promise that resolves to a comment file object
2019
+ * @memberof FAMISDb
2020
+ */
2021
+ async getCommentFile(fileUid) {
2022
+ let response = null;
2023
+ try {
2024
+ const records = await this.db("invoice_comment_files").where({ uid: fileUid });
2025
+ if (records.length)
2026
+ response = records[0];
2027
+ } catch (e) {
2028
+ console.log(e);
2029
+ throw e;
2030
+ }
2031
+ return response;
2032
+ }
2033
+ /**
2034
+ * Represents a row in the public.core_account_emails table.
2035
+ *
2036
+ * @interface CoreAccountEmail
2037
+ * @property {string} uid - The unique identifier for the account email.
2038
+ * @property {number} core_id - The core ID associated with the account email.
2039
+ * @property {string} account_number - The account number associated with the email.
2040
+ * @property {string} email - The email address associated with the account.
2041
+ * @property {Date | null} [created] - The timestamp when the account email was created. Nullable.
2042
+ * @property {Date | null} [deleted] - The timestamp when the account email was deleted. Nullable.
2043
+ */
2044
+ /**
2045
+ * Get additional delivery email to an expense accounts by uid
2046
+ * @param {string} uid - additional delivery email record uid
2047
+ * @returns {Promise<CoreAccountEmail|null>} - Promise that resolves to an account object
2048
+ * @memberof FAMISDb
2049
+ */
2050
+ async getCoreAccountAdditionalEmailByUid(uid) {
2051
+ let response = null;
2052
+ try {
2053
+ const records = await this.db("core_account_emails").where({ uid });
2054
+ response = records.length ? records[0] : records;
2055
+ } catch (e) {
2056
+ console.log(e);
2057
+ throw e;
2058
+ }
2059
+ return response;
2060
+ }
2061
+ /**
2062
+ * Get additional delivery emails to expense accounts for a core by core id
2063
+ * @param {number} coreId - Core ID
2064
+ * @returns {Promise<CoreAccountEmail[]|[]>} - Promise that resolves to an account object
2065
+ * @memberof FAMISDb
2066
+ */
2067
+ async getCoreAccountAdditionalEmailsByCoreId(coreId) {
2068
+ let response = [];
2069
+ try {
2070
+ const records = await this.db("core_account_emails").where({ core_id: coreId });
2071
+ response = records;
2072
+ } catch (e) {
2073
+ console.log(e);
2074
+ throw e;
2075
+ }
2076
+ return response;
2077
+ }
2078
+ /**
2079
+ * Get additional delivery emails to expense accounts for a core by account number
2080
+ * @param {string} accountNumber - 13 digit famis account or customernumber
2081
+ * @returns {Promise<CoreAccountEmail[]|[]>} - Promise that resolves to an account object
2082
+ * @memberof FAMISDb
2083
+ */
2084
+ async getCoreAccountAdditionalEmailsByAccount(accountNumber) {
2085
+ let response = [];
2086
+ try {
2087
+ const records = await this.db("core_account_emails").where({ account_number: accountNumber });
2088
+ response = records;
2089
+ } catch (e) {
2090
+ console.log(e);
2091
+ throw e;
2092
+ }
2093
+ return response;
2094
+ }
2095
+ /**
2096
+ * Add an additional delivery email to an expense account for a core
2097
+ * @param {string} accountNumber - 13 digit famis account or customernumber
2098
+ * @param {number} coreId - Core ID
2099
+ * @param {string} email - email address
2100
+ * @returns {Promise<CoreAccountEmail>} - Promise that resolves to an account object
2101
+ * @memberof FAMISDb
2102
+ */
2103
+ async addCoreAccountAdditionalEmails(accountNumber, coreId, email) {
2104
+ let response = null;
2105
+ const columns = ["uid", "core_id", "account_number", "email", "created", "deleted"];
2106
+ try {
2107
+ const records = await this.db("core_account_emails").returning(columns).insert({
2108
+ core_id: coreId,
2109
+ account_number: accountNumber,
2110
+ email,
2111
+ created: this.db.raw("NOW")
2112
+ });
2113
+ response = records.length ? records[0] : records;
2114
+ } catch (e) {
2115
+ console.log(e);
2116
+ throw e;
2117
+ }
2118
+ return response;
2119
+ }
2120
+ /**
2121
+ * Remove an additional delivery email to an expense account for a core
2122
+ * @param {string} uid - uid for core account email record
2123
+ * @returns {Promise<CoreAccountEmail>} - Promise that resolves to an account object
2124
+ * @memberof FAMISDb
2125
+ */
2126
+ async removeCoreAccountAdditionalEmails(uid) {
2127
+ let response = null;
2128
+ const columns = ["uid", "core_id", "account_number", "email", "created", "deleted"];
2129
+ try {
2130
+ const records = await this.db("core_account_emails").returning(columns).update({
2131
+ deleted: this.db.raw("NOW")
2132
+ }).where({ uid });
2133
+ response = records.length ? records[0] : records;
2134
+ } catch (e) {
2135
+ console.log(e);
2136
+ throw e;
2137
+ }
2138
+ return response;
2139
+ }
2140
+ }
2141
+
2142
+ module.exports = FAMISDb;
2143
+ //# sourceMappingURL=main.cjs.map