Files
CapaKraken/packages/engine/src/__tests__/estimate-export-serializer.test.ts
T

180 lines
4.9 KiB
TypeScript

import {
EstimateExportFormat,
EstimateStatus,
EstimateVersionStatus,
} from "@capakraken/shared";
import { describe, expect, it } from "vitest";
import {
serializeEstimateExport,
type EstimateExportSource,
} from "../estimate/export-serializer.js";
const createdAt = new Date("2026-03-13T08:00:00.000Z");
function buildSource(): EstimateExportSource {
return {
estimate: {
id: "est_1",
projectId: "project_1",
name: "CGI Estimate",
opportunityId: "OP-42",
baseCurrency: "EUR",
status: EstimateStatus.APPROVED,
createdAt,
updatedAt: createdAt,
},
project: {
id: "project_1",
shortCode: "CGI-001",
name: "CGI Project",
status: "ACTIVE",
startDate: "2026-03-01T00:00:00.000Z",
endDate: "2026-04-01T00:00:00.000Z",
},
version: {
id: "ver_1",
versionNumber: 1,
label: "Approved",
status: EstimateVersionStatus.APPROVED,
notes: "Ready for export",
lockedAt: createdAt,
projectSnapshot: {},
createdAt,
updatedAt: createdAt,
assumptions: [
{
id: "assumption_1",
category: "commercial",
key: "pricingStructure",
label: "Pricing Structure",
valueType: "string",
value: "fixed-bid",
sortOrder: 0,
notes: null,
createdAt,
updatedAt: createdAt,
},
],
scopeItems: [
{
id: "scope_1",
sequenceNo: 10,
scopeType: "SHOT",
packageCode: "PKG-1",
name: "Shot 010",
description: "Main hero shot",
scene: "Scene 01",
page: null,
location: "Berlin",
assumptionCategory: null,
technicalSpec: { resolution: "4K" },
frameCount: 120,
itemCount: 1,
unitMode: "shot",
internalComments: null,
externalComments: null,
metadata: {},
createdAt,
updatedAt: createdAt,
},
],
demandLines: [
{
id: "line_1",
scopeItemId: "scope_1",
roleId: "role_1",
resourceId: "resource_1",
lineType: "LABOR",
name: "Comp Artist",
chapter: "Compositing",
hours: 40,
days: 5,
fte: 1,
rateSource: "resource",
costRateCents: 5000,
billRateCents: 8000,
currency: "EUR",
costTotalCents: 200000,
priceTotalCents: 320000,
monthlySpread: { "2026-03": 40 },
staffingAttributes: { location: "Berlin" },
metadata: {},
createdAt,
updatedAt: createdAt,
},
],
resourceSnapshots: [
{
id: "snapshot_1",
resourceId: "resource_1",
sourceEid: "E100",
displayName: "Alex Artist",
chapter: "Compositing",
roleId: "role_1",
currency: "EUR",
lcrCents: 5000,
ucrCents: 8000,
fte: 1,
location: "Berlin",
country: "DE",
level: "Senior",
workType: "Remote",
attributes: {},
createdAt,
updatedAt: createdAt,
},
],
metrics: [
{
id: "metric_1",
key: "total_hours",
label: "Total Hours",
metricGroup: "summary",
valueDecimal: 40,
valueCents: null,
currency: null,
metadata: {},
createdAt,
updatedAt: createdAt,
},
],
},
};
}
describe("estimate export serializer", () => {
it("creates a structured JSON export payload", async () => {
const payload = await serializeEstimateExport(buildSource(), EstimateExportFormat.JSON);
expect(payload.encoding).toBe("utf8");
expect(payload.mimeType).toBe("application/json; charset=utf-8");
expect(payload.summary.totalHours).toBe(40);
expect(payload.content).toContain('"estimateId": "est_1"');
expect(payload.previewText).toContain('"schemaVersion": 1');
});
it("creates a multi-sheet xlsx export payload", async () => {
const payload = await serializeEstimateExport(buildSource(), EstimateExportFormat.XLSX);
const ExcelJS = await import("exceljs");
const workbook = new ExcelJS.Workbook();
const workbookBytes = Uint8Array.from(Buffer.from(payload.content, "base64"));
const workbookBuffer = workbookBytes.buffer.slice(
workbookBytes.byteOffset,
workbookBytes.byteOffset + workbookBytes.byteLength,
);
await workbook.xlsx.load(workbookBuffer);
expect(payload.encoding).toBe("base64");
expect(payload.sheetNames).toEqual([
"Overview",
"Assumptions",
"Scope",
"DemandLines",
"Resources",
"Metrics",
]);
expect(workbook.getWorksheet("DemandLines")).toBeDefined();
expect(payload.byteLength).toBeGreaterThan(100);
});
});