180 lines
4.9 KiB
TypeScript
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);
|
|
});
|
|
});
|