694 lines
20 KiB
TypeScript
694 lines
20 KiB
TypeScript
import {
|
|
EstimateExportFormat,
|
|
type EstimateExportArtifactPayload,
|
|
type EstimateExportSummary,
|
|
type EstimateStatus,
|
|
type EstimateVersionStatus,
|
|
} from "@capakraken/shared";
|
|
import { summarizeEstimateDemandLines } from "./metrics.js";
|
|
|
|
type ExcelJsModule = typeof import("exceljs");
|
|
|
|
type ExportProjectRef = {
|
|
id: string;
|
|
name: string;
|
|
shortCode?: string | null;
|
|
status?: string | null;
|
|
startDate?: Date | string | null;
|
|
endDate?: Date | string | null;
|
|
} | null;
|
|
|
|
type ExportAssumption = {
|
|
id: string;
|
|
category: string;
|
|
key: string;
|
|
label: string;
|
|
valueType: string;
|
|
value: unknown;
|
|
sortOrder: number;
|
|
notes?: string | null;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
};
|
|
|
|
type ExportScopeItem = {
|
|
id: string;
|
|
sequenceNo: number;
|
|
scopeType: string;
|
|
packageCode?: string | null;
|
|
name: string;
|
|
description?: string | null;
|
|
scene?: string | null;
|
|
page?: string | null;
|
|
location?: string | null;
|
|
assumptionCategory?: string | null;
|
|
technicalSpec: unknown;
|
|
frameCount?: number | null;
|
|
itemCount?: number | null;
|
|
unitMode?: string | null;
|
|
internalComments?: string | null;
|
|
externalComments?: string | null;
|
|
metadata: unknown;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
};
|
|
|
|
type ExportDemandLine = {
|
|
id: string;
|
|
scopeItemId?: string | null;
|
|
roleId?: string | null;
|
|
resourceId?: string | null;
|
|
lineType: string;
|
|
name: string;
|
|
chapter?: string | null;
|
|
hours: number;
|
|
days?: number | null;
|
|
fte?: number | null;
|
|
rateSource?: string | null;
|
|
costRateCents: number;
|
|
billRateCents: number;
|
|
currency: string;
|
|
costTotalCents: number;
|
|
priceTotalCents: number;
|
|
monthlySpread: unknown;
|
|
staffingAttributes: unknown;
|
|
metadata: unknown;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
};
|
|
|
|
type ExportResourceSnapshot = {
|
|
id: string;
|
|
resourceId?: string | null;
|
|
sourceEid?: string | null;
|
|
displayName: string;
|
|
chapter?: string | null;
|
|
roleId?: string | null;
|
|
currency: string;
|
|
lcrCents: number;
|
|
ucrCents: number;
|
|
fte?: number | null;
|
|
location?: string | null;
|
|
country?: string | null;
|
|
level?: string | null;
|
|
workType?: string | null;
|
|
attributes: unknown;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
};
|
|
|
|
type ExportMetric = {
|
|
id: string;
|
|
key: string;
|
|
label: string;
|
|
metricGroup?: string | null;
|
|
valueDecimal: number;
|
|
valueCents?: number | null;
|
|
currency?: string | null;
|
|
metadata: unknown;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
};
|
|
|
|
type ExportSheetRow = Record<string, unknown>;
|
|
|
|
let _excelJs: ExcelJsModule | null = null;
|
|
|
|
async function getExcelJS() {
|
|
if (!_excelJs) {
|
|
_excelJs = await import("exceljs");
|
|
}
|
|
|
|
return _excelJs;
|
|
}
|
|
|
|
export interface EstimateExportSource {
|
|
estimate: {
|
|
id: string;
|
|
projectId?: string | null;
|
|
name: string;
|
|
opportunityId?: string | null;
|
|
baseCurrency: string;
|
|
status: EstimateStatus | string;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
};
|
|
version: {
|
|
id: string;
|
|
versionNumber: number;
|
|
label?: string | null;
|
|
status: EstimateVersionStatus | string;
|
|
notes?: string | null;
|
|
lockedAt?: Date | null;
|
|
projectSnapshot: unknown;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
assumptions: ExportAssumption[];
|
|
scopeItems: ExportScopeItem[];
|
|
demandLines: ExportDemandLine[];
|
|
resourceSnapshots: ExportResourceSnapshot[];
|
|
metrics: ExportMetric[];
|
|
};
|
|
project: ExportProjectRef;
|
|
}
|
|
|
|
function serializeDate(value: Date | string | null | undefined) {
|
|
if (value instanceof Date) {
|
|
return value.toISOString();
|
|
}
|
|
|
|
return value ?? null;
|
|
}
|
|
|
|
function stringifyValue(value: unknown) {
|
|
if (value == null) {
|
|
return "";
|
|
}
|
|
if (typeof value === "string") {
|
|
return value;
|
|
}
|
|
if (typeof value === "number" || typeof value === "boolean") {
|
|
return String(value);
|
|
}
|
|
|
|
return JSON.stringify(value);
|
|
}
|
|
|
|
function toNumericRecord(value: unknown) {
|
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
return {} as Record<string, number>;
|
|
}
|
|
|
|
return Object.fromEntries(
|
|
Object.entries(value).filter(
|
|
(entry): entry is [string, number] => typeof entry[1] === "number",
|
|
),
|
|
);
|
|
}
|
|
|
|
function escapeDelimitedValue(value: unknown, delimiter: string) {
|
|
const rendered = stringifyValue(value);
|
|
if (
|
|
rendered.includes(delimiter) ||
|
|
rendered.includes('"') ||
|
|
rendered.includes("\n") ||
|
|
rendered.includes("\r")
|
|
) {
|
|
return `"${rendered.replaceAll('"', '""')}"`;
|
|
}
|
|
|
|
return rendered;
|
|
}
|
|
|
|
function serializeDelimited(
|
|
rows: Array<Record<string, unknown>>,
|
|
delimiter: string,
|
|
) {
|
|
if (rows.length === 0) {
|
|
return "";
|
|
}
|
|
|
|
const columns = Array.from(
|
|
rows.reduce((keys, row) => {
|
|
for (const key of Object.keys(row)) {
|
|
keys.add(key);
|
|
}
|
|
return keys;
|
|
}, new Set<string>()),
|
|
);
|
|
const header = columns.map((column) => escapeDelimitedValue(column, delimiter));
|
|
const body = rows.map((row) =>
|
|
columns.map((column) => escapeDelimitedValue(row[column], delimiter)).join(delimiter),
|
|
);
|
|
|
|
return [header.join(delimiter), ...body].join("\n");
|
|
}
|
|
|
|
function buildSummary(source: EstimateExportSource): EstimateExportSummary {
|
|
const summarized = summarizeEstimateDemandLines(source.version.demandLines);
|
|
const metricsByKey = new Map(
|
|
source.version.metrics.map((metric) => [metric.key, metric]),
|
|
);
|
|
|
|
const totalHours =
|
|
metricsByKey.get("total_hours")?.valueDecimal ?? summarized.totalHours;
|
|
const totalCostCents =
|
|
metricsByKey.get("total_cost")?.valueCents ?? summarized.totalCostCents;
|
|
const totalPriceCents =
|
|
metricsByKey.get("total_price")?.valueCents ?? summarized.totalPriceCents;
|
|
const marginCents =
|
|
metricsByKey.get("margin")?.valueCents ?? summarized.marginCents;
|
|
const marginPercent =
|
|
metricsByKey.get("margin_percent")?.valueDecimal ?? summarized.marginPercent;
|
|
|
|
return {
|
|
estimateId: source.estimate.id,
|
|
estimateName: source.estimate.name,
|
|
versionId: source.version.id,
|
|
versionNumber: source.version.versionNumber,
|
|
versionStatus: source.version.status as EstimateVersionStatus,
|
|
projectId: source.estimate.projectId ?? source.project?.id ?? null,
|
|
projectName: source.project?.name ?? null,
|
|
baseCurrency: source.estimate.baseCurrency,
|
|
assumptionCount: source.version.assumptions.length,
|
|
scopeItemCount: source.version.scopeItems.length,
|
|
demandLineCount: source.version.demandLines.length,
|
|
resourceSnapshotCount: source.version.resourceSnapshots.length,
|
|
totalHours,
|
|
totalCostCents,
|
|
totalPriceCents,
|
|
marginCents,
|
|
marginPercent,
|
|
};
|
|
}
|
|
|
|
function buildOverviewRows(
|
|
source: EstimateExportSource,
|
|
summary: EstimateExportSummary,
|
|
) {
|
|
return [
|
|
{ field: "estimate_id", value: summary.estimateId },
|
|
{ field: "estimate_name", value: summary.estimateName },
|
|
{ field: "estimate_status", value: source.estimate.status },
|
|
{ field: "version_id", value: summary.versionId },
|
|
{ field: "version_number", value: summary.versionNumber },
|
|
{ field: "version_status", value: summary.versionStatus },
|
|
{ field: "version_label", value: source.version.label ?? "" },
|
|
{ field: "version_notes", value: source.version.notes ?? "" },
|
|
{ field: "project_id", value: summary.projectId ?? "" },
|
|
{ field: "project_name", value: summary.projectName ?? "" },
|
|
{ field: "project_code", value: source.project?.shortCode ?? "" },
|
|
{ field: "base_currency", value: summary.baseCurrency },
|
|
{ field: "opportunity_id", value: source.estimate.opportunityId ?? "" },
|
|
{ field: "locked_at", value: serializeDate(source.version.lockedAt) ?? "" },
|
|
{ field: "generated_from_project_start", value: serializeDate(source.project?.startDate) ?? "" },
|
|
{ field: "generated_from_project_end", value: serializeDate(source.project?.endDate) ?? "" },
|
|
{ field: "assumption_count", value: summary.assumptionCount },
|
|
{ field: "scope_item_count", value: summary.scopeItemCount },
|
|
{ field: "demand_line_count", value: summary.demandLineCount },
|
|
{ field: "resource_snapshot_count", value: summary.resourceSnapshotCount },
|
|
{ field: "total_hours", value: summary.totalHours },
|
|
{ field: "total_cost_cents", value: summary.totalCostCents },
|
|
{ field: "total_price_cents", value: summary.totalPriceCents },
|
|
{ field: "margin_cents", value: summary.marginCents },
|
|
{ field: "margin_percent", value: summary.marginPercent },
|
|
];
|
|
}
|
|
|
|
function buildAssumptionRows(assumptions: ExportAssumption[]) {
|
|
return assumptions.map((assumption) => ({
|
|
id: assumption.id,
|
|
category: assumption.category,
|
|
key: assumption.key,
|
|
label: assumption.label,
|
|
value_type: assumption.valueType,
|
|
value: stringifyValue(assumption.value),
|
|
notes: assumption.notes ?? "",
|
|
sort_order: assumption.sortOrder,
|
|
}));
|
|
}
|
|
|
|
function buildScopeRows(scopeItems: ExportScopeItem[]) {
|
|
return scopeItems.map((scopeItem) => ({
|
|
id: scopeItem.id,
|
|
sequence_no: scopeItem.sequenceNo,
|
|
scope_type: scopeItem.scopeType,
|
|
package_code: scopeItem.packageCode ?? "",
|
|
name: scopeItem.name,
|
|
description: scopeItem.description ?? "",
|
|
scene: scopeItem.scene ?? "",
|
|
page: scopeItem.page ?? "",
|
|
location: scopeItem.location ?? "",
|
|
assumption_category: scopeItem.assumptionCategory ?? "",
|
|
frame_count: scopeItem.frameCount ?? "",
|
|
item_count: scopeItem.itemCount ?? "",
|
|
unit_mode: scopeItem.unitMode ?? "",
|
|
technical_spec: stringifyValue(scopeItem.technicalSpec),
|
|
internal_comments: scopeItem.internalComments ?? "",
|
|
external_comments: scopeItem.externalComments ?? "",
|
|
metadata: stringifyValue(scopeItem.metadata),
|
|
}));
|
|
}
|
|
|
|
function buildDemandRows(source: EstimateExportSource) {
|
|
const scopeItemsById = new Map(
|
|
source.version.scopeItems.map((scopeItem) => [scopeItem.id, scopeItem]),
|
|
);
|
|
|
|
return source.version.demandLines.map((line, index) => ({
|
|
line_no: index + 1,
|
|
line_id: line.id,
|
|
scope_item_id: line.scopeItemId ?? "",
|
|
scope_item_name:
|
|
(line.scopeItemId ? scopeItemsById.get(line.scopeItemId)?.name : null) ?? "",
|
|
role_id: line.roleId ?? "",
|
|
resource_id: line.resourceId ?? "",
|
|
line_type: line.lineType,
|
|
name: line.name,
|
|
chapter: line.chapter ?? "",
|
|
hours: line.hours,
|
|
days: line.days ?? "",
|
|
fte: line.fte ?? "",
|
|
rate_source: line.rateSource ?? "",
|
|
cost_rate_cents: line.costRateCents,
|
|
bill_rate_cents: line.billRateCents,
|
|
currency: line.currency,
|
|
cost_total_cents: line.costTotalCents,
|
|
price_total_cents: line.priceTotalCents,
|
|
monthly_spread: stringifyValue(toNumericRecord(line.monthlySpread)),
|
|
staffing_attributes: stringifyValue(line.staffingAttributes),
|
|
metadata: stringifyValue(line.metadata),
|
|
}));
|
|
}
|
|
|
|
function buildResourceRows(resourceSnapshots: ExportResourceSnapshot[]) {
|
|
return resourceSnapshots.map((snapshot) => ({
|
|
id: snapshot.id,
|
|
resource_id: snapshot.resourceId ?? "",
|
|
source_eid: snapshot.sourceEid ?? "",
|
|
display_name: snapshot.displayName,
|
|
chapter: snapshot.chapter ?? "",
|
|
role_id: snapshot.roleId ?? "",
|
|
currency: snapshot.currency,
|
|
lcr_cents: snapshot.lcrCents,
|
|
ucr_cents: snapshot.ucrCents,
|
|
fte: snapshot.fte ?? "",
|
|
location: snapshot.location ?? "",
|
|
country: snapshot.country ?? "",
|
|
level: snapshot.level ?? "",
|
|
work_type: snapshot.workType ?? "",
|
|
attributes: stringifyValue(snapshot.attributes),
|
|
}));
|
|
}
|
|
|
|
function buildMetricRows(metrics: ExportMetric[]) {
|
|
return metrics.map((metric) => ({
|
|
id: metric.id,
|
|
key: metric.key,
|
|
label: metric.label,
|
|
metric_group: metric.metricGroup ?? "",
|
|
value_decimal: metric.valueDecimal,
|
|
value_cents: metric.valueCents ?? "",
|
|
currency: metric.currency ?? "",
|
|
metadata: stringifyValue(metric.metadata),
|
|
}));
|
|
}
|
|
|
|
function buildSapRows(
|
|
source: EstimateExportSource,
|
|
summary: EstimateExportSummary,
|
|
) {
|
|
return source.version.demandLines.map((line, index) => ({
|
|
record_type: "ESTIMATE_LINE",
|
|
estimate_id: summary.estimateId,
|
|
version_number: summary.versionNumber,
|
|
project_code: source.project?.shortCode ?? "",
|
|
project_name: summary.projectName ?? "",
|
|
line_no: index + 1,
|
|
line_name: line.name,
|
|
role_id: line.roleId ?? "",
|
|
resource_id: line.resourceId ?? "",
|
|
chapter: line.chapter ?? "",
|
|
hours: line.hours,
|
|
cost_rate_cents: line.costRateCents,
|
|
bill_rate_cents: line.billRateCents,
|
|
cost_total_cents: line.costTotalCents,
|
|
price_total_cents: line.priceTotalCents,
|
|
currency: line.currency,
|
|
rate_source: line.rateSource ?? "",
|
|
version_status: summary.versionStatus,
|
|
}));
|
|
}
|
|
|
|
function buildMmpRows(
|
|
source: EstimateExportSource,
|
|
summary: EstimateExportSummary,
|
|
) {
|
|
const monthKeys = Array.from(
|
|
new Set(
|
|
source.version.demandLines.flatMap((line) =>
|
|
Object.keys(toNumericRecord(line.monthlySpread)),
|
|
),
|
|
),
|
|
).sort();
|
|
|
|
return source.version.demandLines.map((line, index) => {
|
|
const baseRow: Record<string, unknown> = {
|
|
estimate_id: summary.estimateId,
|
|
version_number: summary.versionNumber,
|
|
project_id: summary.projectId ?? "",
|
|
project_code: source.project?.shortCode ?? "",
|
|
line_no: index + 1,
|
|
line_name: line.name,
|
|
role_id: line.roleId ?? "",
|
|
resource_id: line.resourceId ?? "",
|
|
total_hours: line.hours,
|
|
total_cost_cents: line.costTotalCents,
|
|
total_price_cents: line.priceTotalCents,
|
|
currency: line.currency,
|
|
};
|
|
|
|
for (const monthKey of monthKeys) {
|
|
baseRow[`month_${monthKey}`] =
|
|
toNumericRecord(line.monthlySpread)[monthKey] ?? 0;
|
|
}
|
|
|
|
return baseRow;
|
|
});
|
|
}
|
|
|
|
function buildJsonDocument(
|
|
source: EstimateExportSource,
|
|
summary: EstimateExportSummary,
|
|
) {
|
|
return {
|
|
schemaVersion: 1,
|
|
generatedAt: new Date().toISOString(),
|
|
estimate: {
|
|
...source.estimate,
|
|
createdAt: source.estimate.createdAt.toISOString(),
|
|
updatedAt: source.estimate.updatedAt.toISOString(),
|
|
},
|
|
project: source.project
|
|
? {
|
|
...source.project,
|
|
startDate: serializeDate(source.project.startDate),
|
|
endDate: serializeDate(source.project.endDate),
|
|
}
|
|
: null,
|
|
version: {
|
|
id: source.version.id,
|
|
versionNumber: source.version.versionNumber,
|
|
label: source.version.label ?? null,
|
|
status: source.version.status,
|
|
notes: source.version.notes ?? null,
|
|
lockedAt: serializeDate(source.version.lockedAt),
|
|
projectSnapshot: source.version.projectSnapshot,
|
|
createdAt: source.version.createdAt.toISOString(),
|
|
updatedAt: source.version.updatedAt.toISOString(),
|
|
},
|
|
summary,
|
|
assumptions: source.version.assumptions.map((assumption) => ({
|
|
...assumption,
|
|
createdAt: assumption.createdAt.toISOString(),
|
|
updatedAt: assumption.updatedAt.toISOString(),
|
|
})),
|
|
scopeItems: source.version.scopeItems.map((scopeItem) => ({
|
|
...scopeItem,
|
|
createdAt: scopeItem.createdAt.toISOString(),
|
|
updatedAt: scopeItem.updatedAt.toISOString(),
|
|
})),
|
|
demandLines: source.version.demandLines.map((line) => ({
|
|
...line,
|
|
createdAt: line.createdAt.toISOString(),
|
|
updatedAt: line.updatedAt.toISOString(),
|
|
})),
|
|
resourceSnapshots: source.version.resourceSnapshots.map((snapshot) => ({
|
|
...snapshot,
|
|
createdAt: snapshot.createdAt.toISOString(),
|
|
updatedAt: snapshot.updatedAt.toISOString(),
|
|
})),
|
|
metrics: source.version.metrics.map((metric) => ({
|
|
...metric,
|
|
createdAt: metric.createdAt.toISOString(),
|
|
updatedAt: metric.updatedAt.toISOString(),
|
|
})),
|
|
};
|
|
}
|
|
|
|
function base64ByteLength(content: string) {
|
|
const padding = content.endsWith("==") ? 2 : content.endsWith("=") ? 1 : 0;
|
|
return Math.floor((content.length * 3) / 4) - padding;
|
|
}
|
|
|
|
function buildSheetColumns(rows: ExportSheetRow[]) {
|
|
return Array.from(
|
|
rows.reduce((keys, row) => {
|
|
for (const key of Object.keys(row)) {
|
|
keys.add(key);
|
|
}
|
|
return keys;
|
|
}, new Set<string>()),
|
|
);
|
|
}
|
|
|
|
function toWorksheetCellValue(value: unknown): boolean | Date | number | string {
|
|
if (value == null) {
|
|
return "";
|
|
}
|
|
|
|
if (value instanceof Date) {
|
|
return value;
|
|
}
|
|
|
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
return value;
|
|
}
|
|
|
|
return stringifyValue(value);
|
|
}
|
|
|
|
function appendWorksheetFromRows(
|
|
workbook: InstanceType<ExcelJsModule["Workbook"]>,
|
|
sheetName: string,
|
|
rows: ExportSheetRow[],
|
|
): void {
|
|
const worksheet = workbook.addWorksheet(sheetName);
|
|
const columns = buildSheetColumns(rows);
|
|
|
|
if (columns.length === 0) {
|
|
return;
|
|
}
|
|
|
|
worksheet.addRow(columns);
|
|
|
|
for (const row of rows) {
|
|
worksheet.addRow(columns.map((column) => toWorksheetCellValue(row[column])));
|
|
}
|
|
}
|
|
|
|
function buildTextPayload(
|
|
format: EstimateExportFormat,
|
|
content: string,
|
|
summary: EstimateExportSummary,
|
|
options: {
|
|
mimeType: string;
|
|
fileExtension: string;
|
|
rowCount: number;
|
|
},
|
|
): EstimateExportArtifactPayload {
|
|
const lineCount = content.length === 0 ? 0 : content.split("\n").length;
|
|
|
|
return {
|
|
schemaVersion: 1,
|
|
format,
|
|
mimeType: options.mimeType,
|
|
encoding: "utf8",
|
|
fileExtension: options.fileExtension,
|
|
generatedAt: new Date().toISOString(),
|
|
byteLength: new TextEncoder().encode(content).length,
|
|
rowCount: options.rowCount,
|
|
lineCount,
|
|
previewText: content.split("\n").slice(0, 12).join("\n"),
|
|
content,
|
|
summary,
|
|
};
|
|
}
|
|
|
|
async function buildXlsxPayload(
|
|
source: EstimateExportSource,
|
|
summary: EstimateExportSummary,
|
|
): Promise<EstimateExportArtifactPayload> {
|
|
const overviewRows = buildOverviewRows(source, summary);
|
|
const assumptionRows = buildAssumptionRows(source.version.assumptions);
|
|
const scopeRows = buildScopeRows(source.version.scopeItems);
|
|
const demandRows = buildDemandRows(source);
|
|
const resourceRows = buildResourceRows(source.version.resourceSnapshots);
|
|
const metricRows = buildMetricRows(source.version.metrics);
|
|
const ExcelJS = await getExcelJS();
|
|
const workbook = new ExcelJS.Workbook();
|
|
const sheets = [
|
|
{ name: "Overview", rows: overviewRows },
|
|
{ name: "Assumptions", rows: assumptionRows },
|
|
{ name: "Scope", rows: scopeRows },
|
|
{ name: "DemandLines", rows: demandRows },
|
|
{ name: "Resources", rows: resourceRows },
|
|
{ name: "Metrics", rows: metricRows },
|
|
] as const;
|
|
|
|
for (const sheet of sheets) {
|
|
appendWorksheetFromRows(workbook, sheet.name, sheet.rows);
|
|
}
|
|
|
|
const buffer = await workbook.xlsx.writeBuffer();
|
|
const content = Buffer.from(buffer).toString("base64");
|
|
|
|
return {
|
|
schemaVersion: 1,
|
|
format: EstimateExportFormat.XLSX,
|
|
mimeType:
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
encoding: "base64",
|
|
fileExtension: "xlsx",
|
|
generatedAt: new Date().toISOString(),
|
|
byteLength: base64ByteLength(content),
|
|
rowCount:
|
|
overviewRows.length +
|
|
assumptionRows.length +
|
|
scopeRows.length +
|
|
demandRows.length +
|
|
resourceRows.length +
|
|
metricRows.length,
|
|
lineCount: null,
|
|
sheetNames: sheets.map((sheet) => sheet.name),
|
|
previewText: `Sheets: ${sheets.map((sheet) => sheet.name).join(", ")}`,
|
|
content,
|
|
summary,
|
|
};
|
|
}
|
|
|
|
export async function serializeEstimateExport(
|
|
source: EstimateExportSource,
|
|
format: EstimateExportFormat,
|
|
): Promise<EstimateExportArtifactPayload> {
|
|
const summary = buildSummary(source);
|
|
|
|
if (format === EstimateExportFormat.JSON) {
|
|
const content = JSON.stringify(buildJsonDocument(source, summary), null, 2);
|
|
return buildTextPayload(format, content, summary, {
|
|
mimeType: "application/json; charset=utf-8",
|
|
fileExtension: "json",
|
|
rowCount: summary.demandLineCount,
|
|
});
|
|
}
|
|
|
|
if (format === EstimateExportFormat.CSV) {
|
|
const rows = buildDemandRows(source);
|
|
return buildTextPayload(format, serializeDelimited(rows, ","), summary, {
|
|
mimeType: "text/csv; charset=utf-8",
|
|
fileExtension: "csv",
|
|
rowCount: rows.length,
|
|
});
|
|
}
|
|
|
|
if (format === EstimateExportFormat.SAP) {
|
|
const rows = buildSapRows(source, summary);
|
|
return buildTextPayload(format, serializeDelimited(rows, ";"), summary, {
|
|
mimeType: "text/plain; charset=utf-8",
|
|
fileExtension: "sap",
|
|
rowCount: rows.length,
|
|
});
|
|
}
|
|
|
|
if (format === EstimateExportFormat.MMP) {
|
|
const rows = buildMmpRows(source, summary);
|
|
return buildTextPayload(format, serializeDelimited(rows, "|"), summary, {
|
|
mimeType: "text/plain; charset=utf-8",
|
|
fileExtension: "mmp",
|
|
rowCount: rows.length,
|
|
});
|
|
}
|
|
|
|
return buildXlsxPayload(source, summary);
|
|
}
|