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