scheduler-node-models 1.0.31 → 1.0.33

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scheduler-node-models",
3
- "version": "1.0.31",
3
+ "version": "1.0.33",
4
4
  "main": "./index.js",
5
5
  "types": "./index.d.ts",
6
6
  "files": [
@@ -11,6 +11,7 @@
11
11
  "bcrypt": "^6.0.0",
12
12
  "exceljs": "^4.4.0",
13
13
  "mongodb": "^6.18.0",
14
- "nodemailer": "^7.0.5"
14
+ "nodemailer": "^7.0.5",
15
+ "scheduler-node-models": "^1.0.32"
15
16
  }
16
17
  }
@@ -0,0 +1,66 @@
1
+ import { Workbook, Worksheet } from "exceljs";
2
+ import { Report } from "../../general";
3
+ import { ISite } from "../sites";
4
+ import { CompareWorkCode, IWorkcode, LaborCode } from "../labor";
5
+ import { Employee, IEmployee } from "../employees";
6
+ import { User } from "../../users";
7
+ import { Forecast } from "../sites/reports";
8
+ export declare class ChargeStatusReport extends Report {
9
+ private site;
10
+ private workcodes;
11
+ private lastWorked;
12
+ private fonts;
13
+ private fills;
14
+ private borders;
15
+ private alignments;
16
+ private numformats;
17
+ constructor(isite: ISite, workcodes: IWorkcode[]);
18
+ create(user: User, iEmps: IEmployee[], companyID: string, reqDate: Date): Workbook;
19
+ /**
20
+ * This function will create the basic style information to be used within the sheet
21
+ * cells, which consists for font styles, cell fill styles, borders, and alignments for
22
+ * the text within the cell. Plus a late addition of the various number formats to
23
+ * present.
24
+ */
25
+ createStyles(): void;
26
+ /**
27
+ * This function will create the various pages of the workbook which hold the actual
28
+ * data for the workbook. They will consist of a header section, then list the
29
+ * employee's work/forecast work hours by labor code. lastly, if the stats worksheet
30
+ * is provided, it will add the statistics formulas to the rows of data on this sheet,
31
+ * by report type (current or forecast);
32
+ * @param workbook A reference to the workbook object so worksheet creation is possible
33
+ * @param type A string value to indicate if the report is for current or forecast data
34
+ * @param report The forecast object used to define the period and labor codes for use
35
+ * @param employees The list of site employees used for the report.
36
+ * @param stats A reference to the statistics worksheet to allow this to add the
37
+ * correct formulas to it.
38
+ * @param statsRow The numeric value for the last row used in the statistics worksheet.
39
+ * @return The numeric value for the last row used in the statistics worksheet.
40
+ */
41
+ addReport(workbook: Workbook, type: string, report: Forecast, employees: Employee[], stats?: Worksheet, statsRow?: number): number | undefined;
42
+ /**
43
+ * This function focuses on an employee, who has hours in the labor code provided. This
44
+ * will create a row of data on the worksheet for this employee's work in this
45
+ * category
46
+ * @param sheet The reference to the worksheet to add a row of data to.
47
+ * @param lCode The labor code object, which allows the work to focus on a single code.
48
+ * @param row The numeric value for the worksheet row to use in filling in the data
49
+ * @param emp The employee object to pull the work data.
50
+ * @param report The forecast report object which provides the periods and other data
51
+ * used in constructing the employee's row.
52
+ * @param current The boolean value which signifies whether only actual work performed
53
+ * or add in forecast work hours in this report sheet.
54
+ * @param compare A short workcode array, used to determine leaves and other non-work
55
+ * periods.
56
+ */
57
+ employeeRow(sheet: Worksheet, lCode: LaborCode, row: number, emp: Employee, report: Forecast, current: boolean, compare: CompareWorkCode[]): number;
58
+ /**
59
+ * This function will create the statistics page for the cover of the workbook, which
60
+ * will hold all the formulas for overall labor usage.
61
+ * @param workbook The excel workbook object use for creating worksheets within it.
62
+ * @returns The reference to the statistics worksheet to allow other functions to fill
63
+ * in the formulas to use.
64
+ */
65
+ createStatisticsSheet(workbook: Workbook): Worksheet;
66
+ }
@@ -0,0 +1,716 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ChargeStatusReport = void 0;
4
+ const exceljs_1 = require("exceljs");
5
+ const general_1 = require("../../general");
6
+ const sites_1 = require("../sites");
7
+ const labor_1 = require("../labor");
8
+ const employees_1 = require("../employees");
9
+ class ChargeStatusReport extends general_1.Report {
10
+ site;
11
+ workcodes;
12
+ lastWorked;
13
+ fonts;
14
+ fills;
15
+ borders;
16
+ alignments;
17
+ numformats;
18
+ constructor(isite, workcodes) {
19
+ super();
20
+ this.site = new sites_1.Site(isite);
21
+ this.workcodes = new Map();
22
+ workcodes.forEach(wc => {
23
+ const wCode = new labor_1.Workcode(wc);
24
+ this.workcodes.set(wCode.id, wCode);
25
+ });
26
+ this.lastWorked = new Date(0);
27
+ this.fonts = new Map();
28
+ this.fills = new Map();
29
+ this.borders = new Map();
30
+ this.alignments = new Map();
31
+ this.numformats = new Map();
32
+ }
33
+ create(user, iEmps, companyID, reqDate) {
34
+ const workbook = new exceljs_1.Workbook();
35
+ workbook.creator = user.getFullName();
36
+ workbook.created = new Date();
37
+ // determine the lastworked date for the report
38
+ const employees = [];
39
+ this.lastWorked = new Date(0);
40
+ iEmps.forEach(iEmp => {
41
+ const emp = new employees_1.Employee(iEmp);
42
+ const last = emp.getLastWorkday();
43
+ if (emp.companyinfo.company.toLowerCase() === companyID
44
+ && emp.isActive(reqDate)) {
45
+ if (last.getTime() > this.lastWorked.getTime()) {
46
+ this.lastWorked = new Date(last);
47
+ }
48
+ }
49
+ employees.push(emp);
50
+ });
51
+ employees.sort((a, b) => a.compareTo(b));
52
+ this.createStyles();
53
+ // create the statistics sheet first, so the other sheets can feed it data
54
+ const stats = this.createStatisticsSheet(workbook);
55
+ let statsRow = 3;
56
+ // step through the forecast reports, to see if valid, create current and forecast
57
+ // sheets.
58
+ this.site.forecasts.forEach(rpt => {
59
+ if (rpt.use(reqDate, companyID)) {
60
+ let row = this.addReport(workbook, 'Current', rpt, employees, stats, statsRow);
61
+ if (row) {
62
+ statsRow = row;
63
+ }
64
+ row = this.addReport(workbook, 'Forecast', rpt, employees, stats, statsRow);
65
+ if (row) {
66
+ statsRow = row;
67
+ }
68
+ }
69
+ });
70
+ return workbook;
71
+ }
72
+ /**
73
+ * This function will create the basic style information to be used within the sheet
74
+ * cells, which consists for font styles, cell fill styles, borders, and alignments for
75
+ * the text within the cell. Plus a late addition of the various number formats to
76
+ * present.
77
+ */
78
+ createStyles() {
79
+ // set fonts
80
+ this.fonts.set("bold18", { bold: true, size: 14, color: { argb: 'ff000000' } });
81
+ this.fonts.set("bold14", { bold: true, size: 14, color: { argb: 'ff000000' } });
82
+ this.fonts.set("bold12", { bold: true, size: 12, color: { argb: 'ff000000' } });
83
+ this.fonts.set("white12", { bold: false, size: 12, color: { argb: 'ffffffff' } });
84
+ this.fonts.set("blue12", { bold: true, size: 12, color: { argb: 'ff0000ff' } });
85
+ this.fonts.set("nobold9", { bold: false, size: 9, color: { argb: 'ff000000' } });
86
+ this.fonts.set('notnorm', { bold: false, size: 9, color: { argb: 'ff0070c0' } });
87
+ // set fills
88
+ this.fills.set('black', { type: 'pattern', pattern: 'solid', fgColor: { argb: 'ff000000' } });
89
+ this.fills.set('white', { type: 'pattern', pattern: 'solid', fgColor: { argb: 'ffffffff' } });
90
+ this.fills.set('blue', { type: 'pattern', pattern: 'solid', fgColor: { argb: 'ff99ccff' } });
91
+ this.fills.set('ltblue', { type: 'pattern', pattern: 'solid', fgColor: { argb: 'ffccffff' } });
92
+ this.fills.set('green', { type: 'pattern', pattern: 'solid', fgColor: { argb: 'ff99cc00' } });
93
+ this.fills.set('ltgreen', { type: 'pattern', pattern: 'solid', fgColor: { argb: 'ffccffcc' } });
94
+ this.fills.set('dkblush', { type: 'pattern', pattern: 'solid', fgColor: { argb: 'ffda9694' } });
95
+ this.fills.set('yellow', { type: 'pattern', pattern: 'solid', fgColor: { argb: 'ffffff00' } });
96
+ this.fills.set('ltbrown', { type: 'pattern', pattern: 'solid', fgColor: { argb: 'fffcd5b4' } });
97
+ this.fills.set('gray', { type: 'pattern', pattern: 'solid', fgColor: { argb: 'ffc0c0c0' } });
98
+ this.fills.set('ltgray', { type: 'pattern', pattern: 'solid', fgColor: { argb: 'ffbfbfbf' } });
99
+ // set borders
100
+ this.borders.set('blackthin', {
101
+ top: { style: 'thin', color: { argb: 'ff000000' } },
102
+ left: { style: 'thin', color: { argb: 'ff000000' } },
103
+ bottom: { style: 'thin', color: { argb: 'ff000000' } },
104
+ right: { style: 'thin', color: { argb: 'ff000000' } }
105
+ });
106
+ this.borders.set('blackthinNoRight', {
107
+ top: { style: 'thin', color: { argb: 'ff000000' } },
108
+ left: { style: 'thin', color: { argb: 'ff000000' } },
109
+ bottom: { style: 'thin', color: { argb: 'ff000000' } },
110
+ right: { style: 'thin', color: { argb: 'ffffffff' } }
111
+ });
112
+ this.borders.set('blackthinNoLeft', {
113
+ top: { style: 'thin', color: { argb: 'ff000000' } },
114
+ left: { style: 'thin', color: { argb: 'ffffffff' } },
115
+ bottom: { style: 'thin', color: { argb: 'ff000000' } },
116
+ right: { style: 'thin', color: { argb: 'ff000000' } }
117
+ });
118
+ // set alignments
119
+ this.alignments.set('center', { horizontal: 'center', vertical: 'middle', wrapText: true });
120
+ this.alignments.set('leftctr', { horizontal: 'left', vertical: 'middle', wrapText: true });
121
+ this.alignments.set('rightctr', { horizontal: 'right', vertical: 'middle', wrapText: true });
122
+ this.alignments.set('centerslant', { horizontal: 'center', vertical: 'middle',
123
+ wrapText: false, textRotation: 45 });
124
+ // set number formats
125
+ this.numformats.set('sum', '##0.0;[Red]##0.0;-;@');
126
+ this.numformats.set('monthsum', '_(* #,##0.0_);_(* (#,##0.0);_(* \"-\"??_);_(@_)');
127
+ this.numformats.set('pct', '##0.0%;[Red]##0.0%;-;@');
128
+ }
129
+ /**
130
+ * This function will create the various pages of the workbook which hold the actual
131
+ * data for the workbook. They will consist of a header section, then list the
132
+ * employee's work/forecast work hours by labor code. lastly, if the stats worksheet
133
+ * is provided, it will add the statistics formulas to the rows of data on this sheet,
134
+ * by report type (current or forecast);
135
+ * @param workbook A reference to the workbook object so worksheet creation is possible
136
+ * @param type A string value to indicate if the report is for current or forecast data
137
+ * @param report The forecast object used to define the period and labor codes for use
138
+ * @param employees The list of site employees used for the report.
139
+ * @param stats A reference to the statistics worksheet to allow this to add the
140
+ * correct formulas to it.
141
+ * @param statsRow The numeric value for the last row used in the statistics worksheet.
142
+ * @return The numeric value for the last row used in the statistics worksheet.
143
+ */
144
+ addReport(workbook, type, report, employees, stats, statsRow) {
145
+ const current = (type.toLowerCase() === 'current');
146
+ const sheetLabel = `${report.name}-${type}`;
147
+ // add a worksheet to the workbook and set the page options (landscape, etc)
148
+ const sheet = workbook.addWorksheet(sheetLabel, {
149
+ pageSetup: {
150
+ paperSize: undefined,
151
+ orientation: 'landscape',
152
+ fitToHeight: 1,
153
+ fitToWidth: 1,
154
+ blackAndWhite: false,
155
+ fitToPage: true,
156
+ showGridLines: false,
157
+ horizontalCentered: true,
158
+ verticalCentered: true
159
+ }
160
+ });
161
+ sheet.properties.defaultRowHeight = 20;
162
+ sheet.properties.defaultColWidth = 4;
163
+ sheet.properties.outlineLevelCol = 0;
164
+ // set column widths
165
+ const widths = [8, 10.57, 9, 14.57, 10.57, 20, 18.14, 17.57, 14.57, 14.57, 14.57, 55.14];
166
+ const months = ['January', 'Febuary', 'March', 'April', 'May', 'June', 'July',
167
+ 'August', 'September', 'October', 'November', 'December'];
168
+ widths.forEach((width, w) => {
169
+ sheet.getColumn(w + 1).width = width;
170
+ });
171
+ let columns = 12;
172
+ report.periods.forEach(period => {
173
+ const sumCol = columns + 1;
174
+ const startCol = columns + 2;
175
+ const endCol = startCol + (period.periods.length - 1);
176
+ sheet.getColumn(sumCol).width = 12;
177
+ sheet.getColumn(sumCol).outlineLevel = 0;
178
+ for (let i = startCol; i <= endCol; i++) {
179
+ sheet.getColumn(i).width = 9;
180
+ sheet.getColumn(i).outlineLevel = 1;
181
+ }
182
+ columns = endCol;
183
+ });
184
+ sheet.getColumn(columns + 1).width = 15.43;
185
+ sheet.getColumn(columns + 1).outlineLevel = 0;
186
+ // add page's headers
187
+ let style = {
188
+ fill: this.fills.get('white'),
189
+ font: this.fonts.get("bold18"),
190
+ alignment: this.alignments.get("leftctr"),
191
+ border: this.borders.get("blackthin")
192
+ };
193
+ this.setCell(sheet, "A1", "G1", style, `FFP Labor: CLIN ${report.laborCodes[0].clin}`
194
+ + ` SUMMARY`);
195
+ const formatter = new Intl.DateTimeFormat('en-US', {
196
+ month: '2-digit',
197
+ day: '2-digit',
198
+ year: 'numeric'
199
+ });
200
+ this.setCell(sheet, "A2", "G2", style, `${report.name} Year - `
201
+ + `${formatter.format(report.startDate)} - ${formatter.format(report.endDate)}`);
202
+ style.font = this.fonts.get('bold14');
203
+ this.setCell(sheet, "L1", "L1", style, "Weeks Per Accounting Month");
204
+ this.setCell(sheet, "L2", "L2", style, "Accounting Month");
205
+ this.setCell(sheet, "L3", "L3", style, "Week Ending");
206
+ // Set headers for the employee information columns
207
+ style = {
208
+ fill: this.fills.get('black'),
209
+ font: this.fonts.get('white12'),
210
+ alignment: this.alignments.get('center'),
211
+ border: this.borders.get('blackthin')
212
+ };
213
+ this.setCell(sheet, "A4", "A4", style, "CLIN");
214
+ this.setCell(sheet, "B4", "B4", style, "SLIN");
215
+ this.setCell(sheet, "C4", "C4", style, "Company");
216
+ this.setCell(sheet, "D4", "D4", style, "Location");
217
+ this.setCell(sheet, "E4", "E4", style, "WBS");
218
+ this.setCell(sheet, "F4", "F4", style, "Labor NWA");
219
+ this.setCell(sheet, "G4", "G4", style, "Last Name");
220
+ this.setCell(sheet, "H4", "H4", style, "Labor Category");
221
+ this.setCell(sheet, "I4", "I4", style, "Employee ID");
222
+ this.setCell(sheet, "J4", "J4", style, "PeopleSoft ID");
223
+ this.setCell(sheet, "K4", "K4", style, "Cost Center");
224
+ this.setCell(sheet, "L4", "L4", style, "Comments/Remarks");
225
+ // set headers for periods and sub-periods
226
+ columns = 12;
227
+ report.periods.forEach(period => {
228
+ columns++;
229
+ style = {
230
+ fill: this.fills.get('white'),
231
+ font: this.fonts.get('bold14'),
232
+ alignment: this.alignments.get('center'),
233
+ border: this.borders.get('blackthin')
234
+ };
235
+ this.setCell(sheet, this.getCellID(columns, 1), this.getCellID(columns, 1), style, period.periods.length);
236
+ this.setCell(sheet, this.getCellID(columns, 2), this.getCellID(columns + period.periods.length, 2), style, months[period.month.getMonth()].toUpperCase());
237
+ style.font = this.fonts.get('bold14');
238
+ this.setCell(sheet, this.getCellID(columns, 3), this.getCellID(columns, 3), style, "Month Total");
239
+ style = {
240
+ fill: this.fills.get('dkblush'),
241
+ font: this.fonts.get('bold12'),
242
+ alignment: this.alignments.get('center'),
243
+ border: this.borders.get('blackthin')
244
+ };
245
+ const dFormat = new Intl.DateTimeFormat('en-US', {
246
+ month: 'short',
247
+ day: '2-digit'
248
+ });
249
+ this.setCell(sheet, this.getCellID(columns, 4), this.getCellID(columns, 4), style, dFormat.format(period.month));
250
+ const dtStyle = {
251
+ fill: this.fills.get('white'),
252
+ font: this.fonts.get('bold14'),
253
+ alignment: this.alignments.get('center'),
254
+ border: this.borders.get('blackthin')
255
+ };
256
+ const wkStyle = {
257
+ fill: this.fills.get('yellow'),
258
+ font: this.fonts.get('bold12'),
259
+ alignment: this.alignments.get('center'),
260
+ border: this.borders.get('blackthin')
261
+ };
262
+ period.periods.forEach((prd, p) => {
263
+ let cellID = this.getCellID(columns + p + 1, 3);
264
+ this.setCell(sheet, cellID, cellID, dtStyle, dFormat.format(prd));
265
+ cellID = this.getCellID(columns + p + 1, 4);
266
+ this.setCell(sheet, cellID, cellID, wkStyle, `Week ${p + 1}`);
267
+ });
268
+ columns += period.periods.length;
269
+ });
270
+ // Add a totals column header for the overall totals
271
+ style = {
272
+ fill: this.fills.get('white'),
273
+ font: this.fonts.get('bold12'),
274
+ alignment: this.alignments.get('center'),
275
+ border: this.borders.get('blackthin')
276
+ };
277
+ let cellID = this.getCellID(columns + 1, 4);
278
+ this.setCell(sheet, cellID, cellID, style, 'EAC');
279
+ // create list of leave and work codes for comparison to determine working or
280
+ // not working criteria
281
+ let row = 4;
282
+ const compareCodes = [];
283
+ this.workcodes.forEach(wc => {
284
+ compareCodes.push({
285
+ code: wc.id,
286
+ isLeave: wc.isLeave
287
+ });
288
+ });
289
+ let column = 0;
290
+ // this report is listed by labor code, then employees with hours
291
+ report.laborCodes.forEach(lCode => {
292
+ // step through the employees to see if they have hours for this labor code
293
+ let lastWorked = new Date(0);
294
+ employees.forEach(emp => {
295
+ // employee has used this labor code, if they have some actual or forecast hours.
296
+ const actual = emp.getWorkedHours(report.startDate, report.endDate, lCode.chargeNumber, lCode.extension);
297
+ const forecast = emp.getForecastHours(lCode, report.startDate, report.endDate, compareCodes);
298
+ if (actual > 0 || forecast > 0) {
299
+ row++;
300
+ if (emp.getLastWorkday().getTime() > lastWorked.getTime()) {
301
+ lastWorked = new Date(emp.getLastWorkday());
302
+ }
303
+ const col = this.employeeRow(sheet, lCode, row, emp, report, current, compareCodes);
304
+ if (col > column) {
305
+ column = col;
306
+ }
307
+ }
308
+ });
309
+ if (stats && statsRow) {
310
+ // this is used to create the dates in the statistics labor rows
311
+ const dFormat = new Intl.DateTimeFormat('en-US', {
312
+ month: '2-digit',
313
+ day: '2-digit',
314
+ year: 'numeric'
315
+ });
316
+ const codeText = `${lCode.chargeNumber} ${lCode.extension}`;
317
+ // determine the period to date total hours that should have been used based
318
+ // upon an even spread of hours throughout the period.
319
+ let codeHours = 0.0;
320
+ let totalHours = 0.0;
321
+ if (lCode.startDate && lCode.endDate) {
322
+ const days = Math.ceil((lCode.endDate.getTime() - lCode.startDate.getTime())
323
+ / (24 * 3600000));
324
+ let daysToNow = 0.0;
325
+ if (lastWorked.getTime() > lCode.startDate.getTime()) {
326
+ daysToNow = Math.ceil((lastWorked.getTime() - lCode.startDate.getTime())
327
+ / (24 * 3600000));
328
+ }
329
+ if (lCode.hoursPerEmployee && lCode.minimumEmployees) {
330
+ totalHours = lCode.hoursPerEmployee * lCode.minimumEmployees;
331
+ }
332
+ let perDay = 0.0;
333
+ if (daysToNow > 0) {
334
+ perDay = totalHours / days;
335
+ }
336
+ codeHours = perDay * daysToNow;
337
+ }
338
+ let style = {
339
+ fill: this.fills.get('white'),
340
+ font: this.fonts.get('bold12'),
341
+ alignment: this.alignments.get("center"),
342
+ border: this.borders.get('blackthin')
343
+ };
344
+ if (current) {
345
+ // this branch will add the labor codes and start and end dates, plus the
346
+ // current formulas to the statistics worksheet. It will modify the statsRow
347
+ // value to reflect each row used by labor code. Rows need to alternate fills,
348
+ // so that rows are easier to delinate. Columns A to G.
349
+ statsRow++;
350
+ style.numFmt = undefined;
351
+ const bLight = (statsRow % 2) === 0;
352
+ // labor code, and dates cells first three columns
353
+ if (bLight) {
354
+ style.fill = this.fills.get('gray');
355
+ }
356
+ else {
357
+ style.fill = this.fills.get('white');
358
+ }
359
+ this.setCell(stats, this.getCellID(0, statsRow), this.getCellID(0, statsRow), style, codeText);
360
+ this.setCell(stats, this.getCellID(1, statsRow), this.getCellID(1, statsRow), style, dFormat.format(lCode.startDate));
361
+ this.setCell(stats, this.getCellID(2, statsRow), this.getCellID(2, statsRow), style, dFormat.format(lCode.endDate));
362
+ // next current information next four columns, hours to date, total worked
363
+ // hours, +/- hours, percentage of hours to date.
364
+ if (bLight) {
365
+ style.fill = this.fills.get('blue');
366
+ }
367
+ else {
368
+ style.fill = this.fills.get('ltblue');
369
+ }
370
+ style.numFmt = this.numformats.get('sum');
371
+ this.setCell(stats, this.getCellID(3, statsRow), this.getCellID(3, statsRow), style, codeHours);
372
+ let formula = `SUMIF(${sheetLabel}!${this.getCellID(5, 5)}:`
373
+ + `${this.getCellID(5, row)}, "*${codeText}*", ${sheetLabel}!`
374
+ + `${this.getCellID(column, 5)}:${this.getCellID(column, row)})`;
375
+ let cell = stats.getCell(statsRow, 5);
376
+ cell.style = style;
377
+ cell.value = { formula: formula };
378
+ formula = `${this.getCellID(3, statsRow)} - ${this.getCellID(4, statsRow)}`;
379
+ cell = stats.getCell(statsRow, 6);
380
+ cell.style = style;
381
+ cell.value = { formula: formula };
382
+ formula = `IFERROR(${this.getCellID(5, statsRow)}/`
383
+ + `${this.getCellID(3, statsRow)},"N/A")`;
384
+ cell = stats.getCell(statsRow, 7);
385
+ style.numFmt = this.numformats.get('pct');
386
+ cell.style = style;
387
+ cell.value = { formula: formula };
388
+ }
389
+ else {
390
+ // this branch will add only the forecast data formulas to the statistics. It
391
+ // will first find the correct row based on the labor code, then determine the
392
+ // fill style to use, then add the formula. Columns H to K
393
+ let row = 0;
394
+ for (let r = 3; r <= statsRow && row === 0; r++) {
395
+ const cell = stats.getCell(r, 1);
396
+ if (cell.text.toLowerCase() === codeText.toLowerCase()) {
397
+ row = r;
398
+ }
399
+ }
400
+ // row greater than zero means the labor code was found and
401
+ if (row > 0) {
402
+ const bLight = (row % 2) === 0;
403
+ // forecast information next four columns, total hours, total forecasted
404
+ // hours, +/- hours, percentage of hours to date.
405
+ if (bLight) {
406
+ style.fill = this.fills.get('green');
407
+ }
408
+ else {
409
+ style.fill = this.fills.get('ltgreen');
410
+ }
411
+ style.numFmt = this.numformats.get('sum');
412
+ this.setCell(stats, this.getCellID(7, statsRow), this.getCellID(7, statsRow), style, codeHours);
413
+ let formula = `SUMIF(${sheetLabel}!${this.getCellID(5, 5)}:`
414
+ + `${this.getCellID(5, row)}, "*${codeText}*", ${sheetLabel}!`
415
+ + `${this.getCellID(column, 5)}:${this.getCellID(column, row)})`;
416
+ let cell = stats.getCell(statsRow, 9);
417
+ cell.style = style;
418
+ cell.value = { formula: formula };
419
+ formula = `${this.getCellID(7, statsRow)} - ${this.getCellID(8, statsRow)}`;
420
+ cell = stats.getCell(statsRow, 10);
421
+ cell.style = style;
422
+ cell.value = { formula: formula };
423
+ formula = `IFERROR(${this.getCellID(5, statsRow)}/`
424
+ + `${this.getCellID(3, statsRow)},"N/A")`;
425
+ cell = stats.getCell(statsRow, 11);
426
+ style.numFmt = this.numformats.get('pct');
427
+ cell.style = style;
428
+ cell.value = { formula: formula };
429
+ }
430
+ }
431
+ }
432
+ });
433
+ return statsRow;
434
+ }
435
+ /**
436
+ * This function focuses on an employee, who has hours in the labor code provided. This
437
+ * will create a row of data on the worksheet for this employee's work in this
438
+ * category
439
+ * @param sheet The reference to the worksheet to add a row of data to.
440
+ * @param lCode The labor code object, which allows the work to focus on a single code.
441
+ * @param row The numeric value for the worksheet row to use in filling in the data
442
+ * @param emp The employee object to pull the work data.
443
+ * @param report The forecast report object which provides the periods and other data
444
+ * used in constructing the employee's row.
445
+ * @param current The boolean value which signifies whether only actual work performed
446
+ * or add in forecast work hours in this report sheet.
447
+ * @param compare A short workcode array, used to determine leaves and other non-work
448
+ * periods.
449
+ */
450
+ employeeRow(sheet, lCode, row, emp, report, current, compare) {
451
+ const lastWorked = emp.getLastWorkday();
452
+ // create two style objects for filling in the employee's employment information as
453
+ // pertains to the object
454
+ let style = {
455
+ fill: this.fills.get('white'),
456
+ font: this.fonts.get('bold12'),
457
+ alignment: this.alignments.get('center'),
458
+ border: this.borders.get('blackthin'),
459
+ numFmt: this.numformats.get('sum')
460
+ };
461
+ // lstyle is used for a section of the employee's information which highlights the
462
+ // individual as working in a liaison capability.
463
+ const lStyle = {
464
+ fill: this.fills.get('white'),
465
+ font: this.fonts.get('bold12'),
466
+ alignment: this.alignments.get('leftctr'),
467
+ border: this.borders.get('blackthin')
468
+ };
469
+ if (emp.companyinfo.jobtitle
470
+ && emp.companyinfo.jobtitle?.toLowerCase().indexOf('liaison') >= 0) {
471
+ style.fill = this.fills.get('ltbrown');
472
+ lStyle.fill = this.fills.get('ltbrown');
473
+ }
474
+ // this section sets the various required columns with information about the
475
+ // employment in the labor code.
476
+ this.setCell(sheet, this.getCellID(0, row), this.getCellID(0, row), style, (lCode.clin) ? lCode.clin : '');
477
+ this.setCell(sheet, this.getCellID(1, row), this.getCellID(1, row), style, (lCode.slin) ? lCode.slin : '');
478
+ this.setCell(sheet, this.getCellID(2, row), this.getCellID(2, row), style, (emp.companyinfo.division) ? emp.companyinfo.division.toUpperCase() : '');
479
+ this.setCell(sheet, this.getCellID(3, row), this.getCellID(3, row), style, (lCode.location) ? lCode.location : '');
480
+ this.setCell(sheet, this.getCellID(4, row), this.getCellID(4, row), style, (lCode.wbs) ? lCode.wbs : '');
481
+ this.setCell(sheet, this.getCellID(5, row), this.getCellID(5, row), style, `${lCode.chargeNumber} ${lCode.extension}`);
482
+ this.setCell(sheet, this.getCellID(6, row), this.getCellID(6, row), lStyle, emp.name.lastname);
483
+ this.setCell(sheet, this.getCellID(7, row), this.getCellID(7, row), style, (emp.companyinfo.rank) ? emp.companyinfo.rank : '');
484
+ this.setCell(sheet, this.getCellID(0, row), this.getCellID(0, row), style, emp.companyinfo.employeeid);
485
+ this.setCell(sheet, this.getCellID(0, row), this.getCellID(0, row), style, (emp.companyinfo.alternateid) ? emp.companyinfo.alternateid : '');
486
+ // This section steps though the forecast report periods to provide the hours worked/
487
+ // or forecasted to work, in the period. Each period corresponds to an accounting
488
+ // month and its weekly periods. These are determined by the contract administrator
489
+ // and put in the forecast report object.
490
+ let column = 11;
491
+ const sumlist = []; // used to provide a total hours from the monthly values
492
+ report.periods.forEach(period => {
493
+ column++;
494
+ style = {
495
+ fill: this.fills.get('dkblush'),
496
+ font: this.fonts.get('bold12'),
497
+ alignment: this.alignments.get('rightctr'),
498
+ border: this.borders.get('blackthin'),
499
+ numFmt: this.numformats.get('sum')
500
+ };
501
+ // create the formula to obtain the monthly total of hours and set the formula to
502
+ // the summary cell.
503
+ let formula = '';
504
+ if (period.periods.length > 1) {
505
+ formula = `SUM(${this.getCellID(column + 1, row)}:`
506
+ + `${this.getCellID(column + period.periods.length, row)})`;
507
+ }
508
+ else {
509
+ formula = `${this.getCellID(column + 1, row)}`;
510
+ }
511
+ let cell = sheet.getCell(column, row);
512
+ cell.style = style;
513
+ cell.value = { formula: formula };
514
+ // step through the weekly periods to get the employee's work hours.
515
+ period.periods.forEach(prd => {
516
+ column++;
517
+ let last = new Date(prd);
518
+ let first = new Date(last.getTime() - (7 * 24 * 3600000));
519
+ if (first.getTime() < report.startDate.getTime()) {
520
+ first = new Date(Date.UTC(report.startDate.getFullYear(), report.startDate.getMonth(), report.startDate.getDate()));
521
+ }
522
+ if (last.getTime() > report.endDate.getTime()) {
523
+ last = new Date(Date.UTC(report.endDate.getFullYear(), report.endDate.getMonth(), report.endDate.getDate()));
524
+ }
525
+ style = {
526
+ fill: this.fills.get('ltgray'),
527
+ font: this.fonts.get('bold12'),
528
+ alignment: this.alignments.get('center'),
529
+ border: this.borders.get('blackthin')
530
+ };
531
+ let hours = emp.getWorkedHours(first, new Date(last.getTime() + (24 * 3600000)), lCode.chargeNumber, lCode.extension);
532
+ // if the worksheet displays forecast data and the last date is after the
533
+ // employee's last recorded actual workday, show as a forecast value and get the
534
+ // forecast hours added to the actual hours.
535
+ if (!current) {
536
+ if (last.getTime() > lastWorked.getTime()) {
537
+ style.font = this.fonts.get('blue12');
538
+ }
539
+ hours += emp.getForecastHours(lCode, first, new Date(last.getTime() + (24 * 3600000)), compare);
540
+ }
541
+ let cellID = this.getCellID(column, row);
542
+ // this sets an excel conditional formatting relationship for if the resultant
543
+ // value is equal to zero (0), changes fill color and displays a dash.
544
+ sheet.addConditionalFormatting({
545
+ ref: `${cellID}`,
546
+ rules: [
547
+ {
548
+ type: 'cellIs',
549
+ priority: 1,
550
+ operator: 'equal',
551
+ formulae: [0],
552
+ style: {
553
+ fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: `ffffb5b5` } },
554
+ font: { bold: true, size: 12, color: { argb: `ff000000` } },
555
+ alignment: { horizontal: 'center', vertical: 'middle', wrapText: true },
556
+ border: {
557
+ top: { style: 'thin', color: { argb: 'ff000000' } },
558
+ left: { style: 'thin', color: { argb: 'ff000000' } },
559
+ bottom: { style: 'thin', color: { argb: 'ff000000' } },
560
+ right: { style: 'thin', color: { argb: 'ff000000' } }
561
+ },
562
+ numFmt: "##0.0;[Red]##0.0;-;@"
563
+ }
564
+ }
565
+ ]
566
+ });
567
+ // lastly, set the totals column and conditional style format
568
+ style = {
569
+ fill: this.fills.get('dkblush'),
570
+ font: this.fonts.get('bold12'),
571
+ alignment: this.alignments.get('center'),
572
+ border: this.borders.get('blackthin'),
573
+ numFmt: this.numformats.get('monthsum')
574
+ };
575
+ column++;
576
+ cellID = this.getCellID(column, row);
577
+ formula = '';
578
+ sumlist.forEach(c => {
579
+ if (formula !== '') {
580
+ formula += "+";
581
+ }
582
+ formula += c;
583
+ });
584
+ cell = sheet.getCell(cellID);
585
+ cell.style = style;
586
+ cell.value = { formula: formula };
587
+ // these conditional formatting provides fill color change for if the employee
588
+ // works more than his/her alloted hours or less than alloted hours.
589
+ sheet.addConditionalFormatting({
590
+ ref: `${cellID}`,
591
+ rules: [
592
+ {
593
+ type: 'cellIs',
594
+ priority: 1,
595
+ operator: 'greaterThan',
596
+ formulae: [lCode.hoursPerEmployee],
597
+ style: {
598
+ fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: `ffffb5b5` } },
599
+ font: { bold: true, size: 12, color: { argb: `ff000000` } },
600
+ alignment: { horizontal: 'center', vertical: 'middle', wrapText: true },
601
+ border: {
602
+ top: { style: 'thin', color: { argb: 'ff000000' } },
603
+ left: { style: 'thin', color: { argb: 'ff000000' } },
604
+ bottom: { style: 'thin', color: { argb: 'ff000000' } },
605
+ right: { style: 'thin', color: { argb: 'ff000000' } }
606
+ },
607
+ numFmt: "##0.0;[Red]##0.0;-;@"
608
+ }
609
+ }
610
+ ]
611
+ });
612
+ sheet.addConditionalFormatting({
613
+ ref: `${cellID}`,
614
+ rules: [
615
+ {
616
+ type: 'cellIs',
617
+ priority: 1,
618
+ operator: 'lessThan',
619
+ formulae: [lCode.hoursPerEmployee],
620
+ style: {
621
+ fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: `ff99cc00` } },
622
+ font: { bold: true, size: 12, color: { argb: `ff000000` } },
623
+ alignment: { horizontal: 'center', vertical: 'middle', wrapText: true },
624
+ border: {
625
+ top: { style: 'thin', color: { argb: 'ff000000' } },
626
+ left: { style: 'thin', color: { argb: 'ff000000' } },
627
+ bottom: { style: 'thin', color: { argb: 'ff000000' } },
628
+ right: { style: 'thin', color: { argb: 'ff000000' } }
629
+ },
630
+ numFmt: "##0.0;[Red]##0.0;-;@"
631
+ }
632
+ }
633
+ ]
634
+ });
635
+ });
636
+ });
637
+ return column;
638
+ }
639
+ /**
640
+ * This function will create the statistics page for the cover of the workbook, which
641
+ * will hold all the formulas for overall labor usage.
642
+ * @param workbook The excel workbook object use for creating worksheets within it.
643
+ * @returns The reference to the statistics worksheet to allow other functions to fill
644
+ * in the formulas to use.
645
+ */
646
+ createStatisticsSheet(workbook) {
647
+ // Create the sheet just like the others but with the label statistics
648
+ const sheetname = "Statistics";
649
+ const sheet = workbook.addWorksheet(sheetname, {
650
+ pageSetup: {
651
+ paperSize: undefined,
652
+ orientation: 'landscape',
653
+ fitToHeight: 1,
654
+ fitToWidth: 1,
655
+ blackAndWhite: false,
656
+ fitToPage: true,
657
+ showGridLines: false,
658
+ horizontalCentered: true,
659
+ verticalCentered: true
660
+ }
661
+ });
662
+ // set the width of all the columns
663
+ sheet.getColumn("A").width = 30;
664
+ for (let i = 2; i < 12; i++) {
665
+ sheet.getColumn(i).width = 12;
666
+ }
667
+ // this is used to create the current as of date on the sheet
668
+ const dFormat = new Intl.DateTimeFormat('en-US', {
669
+ day: '2-digit',
670
+ month: 'short',
671
+ year: 'numeric'
672
+ });
673
+ let style = {
674
+ fill: this.fills.get('white'),
675
+ font: this.fonts.get('bold18'),
676
+ alignment: this.alignments.get('center'),
677
+ border: this.borders.get('blackthin'),
678
+ };
679
+ const now = new Date();
680
+ this.setCell(sheet, "A1", "K1", style, `Current As Of ${dFormat.format(now)}`);
681
+ sheet.getRow(1).height = 20;
682
+ sheet.getRow(2).height = 77;
683
+ style = {
684
+ fill: this.fills.get('white'),
685
+ font: this.fonts.get('bold12'),
686
+ alignment: this.alignments.get('centerslant'),
687
+ border: this.borders.get("borderthin")
688
+ };
689
+ // Set the column headers with column B & C, inclusive dates in white/slanted 45
690
+ this.setCell(sheet, "B2", "B2", style, "Start");
691
+ this.setCell(sheet, "C2", "C2", style, "End");
692
+ // To Date section headers in a blue/slant 45
693
+ style.fill = this.fills.get('blue');
694
+ this.setCell(sheet, "D2", "D2", style, "Alloted");
695
+ this.setCell(sheet, "E2", "E2", style, "Used");
696
+ this.setCell(sheet, "F2", "F2", style, "Over/Under");
697
+ this.setCell(sheet, "G2", "G2", style, "Percent");
698
+ // Overall/Projected section headers in a blue/slant 45
699
+ style.fill = this.fills.get('green');
700
+ this.setCell(sheet, "H2", "H2", style, "Alloted");
701
+ this.setCell(sheet, "I2", "I2", style, "Used");
702
+ this.setCell(sheet, "J2", "J2", style, "Over/Under");
703
+ this.setCell(sheet, "K2", "K2", style, "Percent");
704
+ // labels for the sections
705
+ style.fill = this.fills.get('white');
706
+ style.alignment = this.alignments.get('center');
707
+ this.setCell(sheet, "A3", "A3", style, "Contract No/Ext");
708
+ this.setCell(sheet, "B3", "C3", style, "Contract Period");
709
+ style.fill = this.fills.get('blue');
710
+ this.setCell(sheet, "D3", "G3", style, "Current");
711
+ style.fill = this.fills.get('green');
712
+ this.setCell(sheet, "H3", "K3", style, "Forecast");
713
+ return sheet;
714
+ }
715
+ }
716
+ exports.ChargeStatusReport = ChargeStatusReport;
@@ -1,3 +1,4 @@
1
1
  export * from './cofsReports';
2
2
  export * from './enterpriseSchedule';
3
3
  export * from './scheduleReport';
4
+ export * from './chargeStatus';
@@ -17,3 +17,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./cofsReports"), exports);
18
18
  __exportStar(require("./enterpriseSchedule"), exports);
19
19
  __exportStar(require("./scheduleReport"), exports);
20
+ __exportStar(require("./chargeStatus"), exports);
@@ -45,4 +45,13 @@ export declare class Forecast implements IForecast {
45
45
  * @param dt The date object for the sub period to add
46
46
  */
47
47
  addOutofCycleSubPeriod(dt: Date): void;
48
+ /**
49
+ * This function is used to determine if a forecast report is to be used for a report
50
+ * date requested.
51
+ * @param date The date object used for the testing.
52
+ * @param company The string value for company identifier for the testing.
53
+ * @returns A boolean value to indicate if the date is between the start and end dates
54
+ * of this forecast report and is for the company indicated.
55
+ */
56
+ use(date: Date, company: string): boolean;
48
57
  }
@@ -188,5 +188,18 @@ class Forecast {
188
188
  prd.periods.push(new Date(dt));
189
189
  }
190
190
  }
191
+ /**
192
+ * This function is used to determine if a forecast report is to be used for a report
193
+ * date requested.
194
+ * @param date The date object used for the testing.
195
+ * @param company The string value for company identifier for the testing.
196
+ * @returns A boolean value to indicate if the date is between the start and end dates
197
+ * of this forecast report and is for the company indicated.
198
+ */
199
+ use(date, company) {
200
+ return (date.getTime() >= this.startDate.getTime()
201
+ && date.getTime() <= this.endDate.getTime()
202
+ && company.toLowerCase() === this.companyid.toLowerCase());
203
+ }
191
204
  }
192
205
  exports.Forecast = Forecast;