Skip to content

Commit 66872b4

Browse files
atGit2021CarsonF
andauthored
PnP: Mismatched Reporting Quarter / Missing Summary % -> 0 (#3324)
Co-authored-by: Carson Full <[email protected]>
1 parent 9f4bd4b commit 66872b4

File tree

3 files changed

+68
-65
lines changed

3 files changed

+68
-65
lines changed

src/common/fiscal-year.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { range } from 'lodash';
22
import { DateTime } from 'luxon';
33
import { CalendarDate, DateInterval } from './temporal';
44

5+
export const fiscalQuarterLabel = (date: CalendarDate) =>
6+
`Q${fiscalQuarter(date)} FY${fiscalYear(date)}`;
7+
58
export const fiscalYear = (dt: DateTime) => dt.year + (dt.month >= 10 ? 1 : 0);
69

710
export const fiscalYears = (start?: DateTime, end?: DateTime) =>
@@ -44,3 +47,12 @@ export const fullFiscalQuarter = (
4447
fiscalQuarterStartDate.endOf('quarter'),
4548
);
4649
};
50+
51+
export const isReasonableYear = (year: unknown) =>
52+
isInt(year) && year >= 1970 && year <= 3000;
53+
54+
export const isQuarterNumber = (quarter: unknown) =>
55+
isInt(quarter) && quarter >= 1 && quarter <= 4;
56+
57+
// Not true for falsy case, thus not built-in, but fine for our cases here.
58+
const isInt = (x: unknown): x is number => Number.isInteger(x);

src/components/pnp/progress-sheet.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { LazyGetter as Once } from 'lazy-get-decorator';
2+
import { fullFiscalQuarter, isQuarterNumber, isReasonableYear } from '~/common';
23
import { Column, Range, Row, Sheet, WorkBook } from '~/common/xlsx.util';
34

45
export abstract class ProgressSheet extends Sheet {
@@ -48,6 +49,25 @@ export abstract class ProgressSheet extends Sheet {
4849
return this.namedRange('PrcntFinishedYears');
4950
}
5051

52+
/**
53+
* This is just an artificial filter.
54+
* It can be greater than the quarter we need, and we can still extract data fine.
55+
*/
56+
@Once() get reportingQuarter() {
57+
const year = this.reportingQuarterCells.year.asNumber!;
58+
const quarterStr = this.reportingQuarterCells.quarter.asString;
59+
const quarter = Number(quarterStr?.slice(1));
60+
return isReasonableYear(year) && isQuarterNumber(quarter)
61+
? fullFiscalQuarter(quarter, year)
62+
: undefined;
63+
}
64+
@Once() get reportingQuarterCells() {
65+
return {
66+
quarter: this.namedRange('RptQtr').start,
67+
year: this.namedRange('RptYr').start,
68+
};
69+
}
70+
5171
columnForQuarterSummary(fiscalQuarter: number) {
5272
return this.book.namedRange(`Q${fiscalQuarter}Column`).start.column;
5373
}

src/components/progress-summary/progress-summary.extractor.ts

Lines changed: 36 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { Injectable } from '@nestjs/common';
2-
import { Nil } from '@seedcompany/common';
2+
import { oneLine } from 'common-tags';
33
import { clamp, round } from 'lodash';
4-
import { CalendarDate, fiscalQuarter, fiscalYear } from '~/common';
4+
import {
5+
CalendarDate,
6+
fiscalQuarter,
7+
fiscalQuarterLabel,
8+
fiscalYear,
9+
} from '~/common';
510
import { Column, Row } from '~/common/xlsx.util';
611
import { Downloadable } from '../file/dto';
712
import { Pnp, ProgressSheet } from '../pnp';
@@ -18,26 +23,33 @@ export class ProgressSummaryExtractor {
1823
const pnp = await Pnp.fromDownloadable(file);
1924
const sheet = pnp.progress;
2025

26+
if (!(sheet.reportingQuarter && date <= sheet.reportingQuarter.end)) {
27+
const cells = sheet.reportingQuarterCells;
28+
result.addProblem({
29+
severity: 'Error',
30+
groups: 'Mismatched Reporting Quarter',
31+
message: oneLine`
32+
The PnP's Reporting Quarter
33+
(_${
34+
sheet.reportingQuarter
35+
? fiscalQuarterLabel(sheet.reportingQuarter.start)
36+
: 'undetermined'
37+
}_ \`${cells.quarter.ref}\`/\`${cells.year.ref}\`)
38+
needs to be *at least* the quarter of this CORD report
39+
(_${fiscalQuarterLabel(date)}_).
40+
`,
41+
source: cells.quarter,
42+
});
43+
return {};
44+
}
45+
2146
const currentFiscalYear = fiscalYear(date);
2247
const yearRow = findFiscalYearRow(sheet, currentFiscalYear);
2348
const quarterCol = sheet.columnForQuarterSummary(fiscalQuarter(date));
2449
return {
25-
reportPeriod: summaryFrom(
26-
yearRow,
27-
quarterCol,
28-
quarterCol,
29-
result,
30-
'Quarterly',
31-
currentFiscalYear,
32-
),
33-
fiscalYear: summaryFrom(
34-
yearRow,
35-
...sheet.columnsForFiscalYear,
36-
result,
37-
'Yearly',
38-
currentFiscalYear,
39-
),
40-
cumulative: findLatestCumulative(yearRow, result, currentFiscalYear),
50+
reportPeriod: summaryFrom(yearRow, quarterCol, quarterCol),
51+
fiscalYear: summaryFrom(yearRow, ...sheet.columnsForFiscalYear),
52+
cumulative: findLatestCumulative(yearRow),
4153
};
4254
}
4355
}
@@ -51,21 +63,11 @@ const findFiscalYearRow = (sheet: ProgressSheet, fiscalYear: number) => {
5163
throw new Error('Unable to find fiscal year in pnp file');
5264
};
5365

54-
const findLatestCumulative = (
55-
currentYear: Row<ProgressSheet>,
56-
result: PnpProgressExtractionResult,
57-
currentFiscalYear: number,
58-
) => {
66+
const findLatestCumulative = (currentYear: Row<ProgressSheet>) => {
5967
const { sheet } = currentYear;
6068
// eslint-disable-next-line no-constant-condition
6169
while (true) {
62-
const summary = summaryFrom(
63-
currentYear,
64-
...sheet.columnsForCumulative,
65-
result,
66-
'Cumulative',
67-
currentFiscalYear,
68-
);
70+
const summary = summaryFrom(currentYear, ...sheet.columnsForCumulative);
6971
if (summary) {
7072
return summary;
7173
}
@@ -80,41 +82,10 @@ const summaryFrom = (
8082
fiscalYear: Row<ProgressSheet>,
8183
plannedColumn: Column,
8284
actualColumn: Column,
83-
result: PnpProgressExtractionResult,
84-
period: string,
85-
currentFiscalYear: number,
86-
): Progress | null => {
87-
const plannedCell = fiscalYear.cell(plannedColumn);
88-
const actualCell = fiscalYear.cell(actualColumn);
89-
let planned = plannedCell.asNumber;
90-
let actual = actualCell.asNumber;
91-
if (!planned && +plannedColumn !== +actualColumn) {
92-
result.addProblem({
93-
severity: 'Error',
94-
groups: [
95-
'Missing progress summary percents',
96-
`The _${period}_ summary percents for _FY${currentFiscalYear}_ is missing`,
97-
],
98-
message: `The _${period} Planned_ percent for _FY${currentFiscalYear}_ \`${plannedCell.ref}\` is missing`,
99-
source: plannedCell,
100-
});
101-
}
102-
if (!actual) {
103-
result.addProblem({
104-
severity: 'Error',
105-
groups: [
106-
'Missing progress summary percents',
107-
`The _${period}_ summary percents for _FY${currentFiscalYear}_ is missing`,
108-
],
109-
message: `The _${period} Actual_ percent for _FY${currentFiscalYear}_ \`${actualCell.ref}\` is missing`,
110-
source: actualCell,
111-
});
112-
}
113-
if (!planned && !actual) {
114-
return null;
115-
}
116-
const normalize = (val: number | Nil) => {
117-
if (!val) return 0;
85+
): Progress => {
86+
let planned = fiscalYear.cell(plannedColumn).asNumber ?? 0;
87+
let actual = fiscalYear.cell(actualColumn).asNumber ?? 0;
88+
const normalize = (val: number) => {
11889
if (round(val, 4) > 1) {
11990
// The PnP has the macro option to do calculations as percents or verse equivalents.
12091
// We need to standardize as percents here.

0 commit comments

Comments
 (0)