src/features/exports/month-export.ts
Linked repository file from this build.
import { AccessError, canAccessReview, can } from "@/lib/auth/scope";
import type { SnapReview } from "@/programs/snap-negative/types";
import type { Actor } from "@/features/reviews/types";
import {
buildEppHandoff,
CURRENT_TEMPLATE_FNS_DISCLAIMER,
currentTemplateFnsRow,
downstreamGateStatus,
transmittalEligible,
EPP_ADAPTER_DISCLAIMER,
EPP_VERIFIED_COLUMN_KEYS,
FNS_TEMPLATE_COLUMNS,
} from "@/features/reviews/downstream";
import { RuleError } from "@/lib/errors";
export type ExportKind = "fns" | "epp";
export function assertExportRole(actor: Actor, kind?: ExportKind) {
if (
!can(actor, "export") ||
(kind === "fns" && actor.role !== "statistics") ||
(kind === "epp" && !can(actor, "exportEpp"))
)
throw new AccessError("This role cannot create this export.");
}
export function parseExportMonths(values: string[]) {
if (!values.length || values.some((value) => !/^\d{4}-(0[1-9]|1[0-2])$/.test(value)))
throw new RuleError("Select at least one valid sample month (YYYY-MM).");
return [...new Set(values)].sort();
}
export function exportMonthSummary(reviews: SnapReview[], actor: Actor) {
assertExportRole(actor);
const months = new Map<
string,
{
month: string;
total: number;
eligible: number;
incomplete: number;
dropped: number;
awaitingRelease: number;
}
>();
for (const review of reviews) {
if (!canAccessReview(actor, review)) continue;
const row = months.get(review.sampleMonth) ?? {
month: review.sampleMonth,
total: 0,
eligible: 0,
incomplete: 0,
dropped: 0,
awaitingRelease: 0,
};
row.total++;
if (transmittalEligible(review)) row.eligible++;
else if (review.state === "dropped" || review.outcome === "drop") row.dropped++;
else if (review.state === "awaiting_cao_response") row.awaitingRelease++;
else row.incomplete++;
months.set(row.month, row);
}
return [...months.values()].sort((a, b) => a.month.localeCompare(b.month));
}
/** Quote TSV control characters and neutralize spreadsheet formulas in user data. */
export function escapeTsvCell(value: unknown): string {
let text = String(value ?? "");
if (/^\s*[=+@-]/.test(text)) text = `'${text}`;
return /[\t\r\n"]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
}
export function generateMonthExport(
reviews: SnapReview[],
actor: Actor,
kind: ExportKind,
selectedMonths: string[],
) {
assertExportRole(actor, kind);
const months = parseExportMonths(selectedMonths);
const rows = reviews
.filter(
(review) =>
canAccessReview(actor, review) &&
months.includes(review.sampleMonth) &&
(kind === "fns" ? transmittalEligible(review) : downstreamGateStatus(review).ok),
)
.sort(
(a, b) =>
a.sampleMonth.localeCompare(b.sampleMonth) || a.reviewNumber.localeCompare(b.reviewNumber),
);
if (!rows.length)
throw new RuleError(
"No released reviews or manager-approved drops in the selected sample months.",
);
let cells: unknown[][];
if (kind === "fns") {
cells = [
FNS_TEMPLATE_COLUMNS.map((column) => column.header),
FNS_TEMPLATE_COLUMNS.map((column) => column.item),
...rows.map(currentTemplateFnsRow),
];
} else {
cells = [
[...EPP_VERIFIED_COLUMN_KEYS],
...rows.map((review) => {
const payload = buildEppHandoff(review, actor);
return EPP_VERIFIED_COLUMN_KEYS.map((key) => payload.columns[key]);
}),
];
}
return {
filename: `${kind === "fns" ? "transmittal" : kind}-${months.join("_")}.tsv`,
mimeType: "text/tab-separated-values;charset=utf-8",
content: cells.map((row) => row.map(escapeTsvCell).join("\t")).join("\r\n") + "\r\n",
count: rows.length,
months,
disclaimer: kind === "fns" ? CURRENT_TEMPLATE_FNS_DISCLAIMER : EPP_ADAPTER_DISCLAIMER,
};
}