chore(repo): initialize planarchy workspace

This commit is contained in:
2026-03-14 14:31:09 +01:00
commit dd55d0e78b
769 changed files with 166461 additions and 0 deletions
@@ -0,0 +1,363 @@
import { describe, expect, it } from "vitest";
import {
CreateRateCardSchema,
CreateRateCardLineSchema,
UpdateRateCardSchema,
} from "../schemas/rate-card.schema.js";
import {
CreateAllocationBaseSchema,
CreateDemandRequirementBaseSchema,
CreateAssignmentBaseSchema,
CreateAllocationSchema,
CreateDemandRequirementSchema,
CreateAssignmentSchema,
FillDemandRequirementSchema,
ShiftProjectSchema,
UpdateAllocationHoursSchema,
} from "../schemas/allocation.schema.js";
import {
CreateEstimateSchema,
EstimateDemandLineSchema,
ScopeItemSchema,
EstimateAssumptionSchema,
ResourceCostSnapshotSchema,
EstimateMetricSchema,
EstimateExportSummarySchema,
} from "../schemas/estimate.schema.js";
import { AllocationStatus, EstimateStatus, EstimateVersionStatus, EstimateExportFormat } from "../types/enums.js";
// ─── Rate Card Schemas ──────────────────────────────────────────────────────
describe("CreateRateCardSchema", () => {
it("accepts valid input with defaults", () => {
const result = CreateRateCardSchema.parse({ name: "Standard Rates" });
expect(result.name).toBe("Standard Rates");
expect(result.currency).toBe("EUR");
expect(result.lines).toEqual([]);
});
it("accepts full input with lines and clientId", () => {
const result = CreateRateCardSchema.parse({
name: "Client Rate Card",
currency: "USD",
clientId: "client-1",
effectiveFrom: "2026-01-01",
effectiveTo: "2026-12-31",
source: "Import",
lines: [{ costRateCents: 5000 }],
});
expect(result.clientId).toBe("client-1");
expect(result.lines).toHaveLength(1);
expect(result.effectiveFrom).toBeInstanceOf(Date);
});
it("rejects empty name", () => {
expect(() => CreateRateCardSchema.parse({ name: "" })).toThrow();
});
it("rejects invalid currency length", () => {
expect(() =>
CreateRateCardSchema.parse({ name: "Test", currency: "EURO" }),
).toThrow();
});
});
describe("CreateRateCardLineSchema", () => {
it("accepts minimal input", () => {
const result = CreateRateCardLineSchema.parse({ costRateCents: 8000 });
expect(result.costRateCents).toBe(8000);
expect(result.attributes).toEqual({});
});
it("accepts full input with all optional fields", () => {
const result = CreateRateCardLineSchema.parse({
roleId: "role-1",
chapter: "3D",
location: "Munich",
seniority: "Senior",
workType: "Production",
serviceGroup: "CGI",
costRateCents: 8000,
billRateCents: 12000,
machineRateCents: 500,
attributes: { tier: "A" },
});
expect(result.chapter).toBe("3D");
expect(result.billRateCents).toBe(12000);
});
it("rejects negative cost rate", () => {
expect(() => CreateRateCardLineSchema.parse({ costRateCents: -1 })).toThrow();
});
it("rejects non-integer cost rate", () => {
expect(() => CreateRateCardLineSchema.parse({ costRateCents: 100.5 })).toThrow();
});
});
describe("UpdateRateCardSchema", () => {
it("accepts partial updates", () => {
const result = UpdateRateCardSchema.parse({ name: "Updated" });
expect(result.name).toBe("Updated");
expect(result.currency).toBeUndefined();
});
it("accepts nullable clientId", () => {
const result = UpdateRateCardSchema.parse({ clientId: null });
expect(result.clientId).toBeNull();
});
it("accepts nullable effectiveFrom", () => {
const result = UpdateRateCardSchema.parse({ effectiveFrom: null });
expect(result.effectiveFrom).toBeNull();
});
});
// ─── Allocation Schemas ─────────────────────────────────────────────────────
describe("CreateDemandRequirementSchema", () => {
const validDemand = {
projectId: "proj-1",
startDate: "2026-03-01",
endDate: "2026-03-31",
hoursPerDay: 8,
percentage: 100,
};
it("accepts valid demand requirement", () => {
const result = CreateDemandRequirementSchema.parse(validDemand);
expect(result.projectId).toBe("proj-1");
expect(result.headcount).toBe(1);
expect(result.status).toBe(AllocationStatus.PROPOSED);
expect(result.metadata).toEqual({});
});
it("rejects end date before start date", () => {
expect(() =>
CreateDemandRequirementSchema.parse({
...validDemand,
startDate: "2026-03-31",
endDate: "2026-03-01",
}),
).toThrow("End date must be after start date");
});
it("rejects hoursPerDay > 24", () => {
expect(() =>
CreateDemandRequirementBaseSchema.parse({ ...validDemand, hoursPerDay: 25 }),
).toThrow();
});
it("rejects percentage > 100", () => {
expect(() =>
CreateDemandRequirementBaseSchema.parse({ ...validDemand, percentage: 101 }),
).toThrow();
});
});
describe("CreateAssignmentSchema", () => {
const validAssignment = {
resourceId: "res-1",
projectId: "proj-1",
startDate: "2026-03-01",
endDate: "2026-03-31",
hoursPerDay: 8,
percentage: 100,
};
it("accepts valid assignment", () => {
const result = CreateAssignmentSchema.parse(validAssignment);
expect(result.resourceId).toBe("res-1");
expect(result.status).toBe(AllocationStatus.PROPOSED);
});
it("requires resourceId", () => {
const { resourceId: _, ...withoutResource } = validAssignment;
expect(() => CreateAssignmentBaseSchema.parse(withoutResource)).toThrow();
});
it("rejects end date before start date", () => {
expect(() =>
CreateAssignmentSchema.parse({
...validAssignment,
startDate: "2026-03-31",
endDate: "2026-03-01",
}),
).toThrow("End date must be after start date");
});
});
describe("FillDemandRequirementSchema", () => {
it("accepts valid fill input", () => {
const result = FillDemandRequirementSchema.parse({
demandRequirementId: "demand-1",
resourceId: "res-1",
});
expect(result.demandRequirementId).toBe("demand-1");
expect(result.hoursPerDay).toBeUndefined();
});
it("rejects hoursPerDay below 0.5", () => {
expect(() =>
FillDemandRequirementSchema.parse({
demandRequirementId: "d-1",
resourceId: "r-1",
hoursPerDay: 0.3,
}),
).toThrow();
});
});
describe("ShiftProjectSchema", () => {
it("accepts valid shift", () => {
const result = ShiftProjectSchema.parse({
projectId: "proj-1",
newStartDate: "2026-04-01",
newEndDate: "2026-06-30",
});
expect(result.projectId).toBe("proj-1");
expect(result.newStartDate).toBeInstanceOf(Date);
});
it("rejects end before start", () => {
expect(() =>
ShiftProjectSchema.parse({
projectId: "proj-1",
newStartDate: "2026-06-30",
newEndDate: "2026-04-01",
}),
).toThrow("New end date must be after new start date");
});
});
// ─── Estimate Schemas ───────────────────────────────────────────────────────
describe("CreateEstimateSchema", () => {
it("accepts minimal input", () => {
const result = CreateEstimateSchema.parse({ name: "Q2 Estimate" });
expect(result.name).toBe("Q2 Estimate");
expect(result.baseCurrency).toBe("EUR");
expect(result.status).toBe(EstimateStatus.DRAFT);
expect(result.assumptions).toEqual([]);
expect(result.scopeItems).toEqual([]);
expect(result.demandLines).toEqual([]);
expect(result.resourceSnapshots).toEqual([]);
expect(result.metrics).toEqual([]);
});
it("rejects empty name", () => {
expect(() => CreateEstimateSchema.parse({ name: "" })).toThrow();
});
});
describe("EstimateDemandLineSchema", () => {
it("accepts minimal demand line", () => {
const result = EstimateDemandLineSchema.parse({
name: "3D Artist",
hours: 160,
});
expect(result.name).toBe("3D Artist");
expect(result.hours).toBe(160);
expect(result.lineType).toBe("LABOR");
expect(result.currency).toBe("EUR");
expect(result.costRateCents).toBe(0);
expect(result.monthlySpread).toEqual({});
expect(result.metadata).toEqual({});
});
it("accepts full demand line with metadata", () => {
const result = EstimateDemandLineSchema.parse({
name: "Senior 3D Artist",
hours: 320,
days: 40,
fte: 1,
roleId: "role-1",
resourceId: "res-1",
chapter: "3D Visualization",
costRateCents: 8000,
billRateCents: 12000,
costTotalCents: 256_000_0,
priceTotalCents: 384_000_0,
monthlySpread: { "2026-03": 80, "2026-04": 80 },
metadata: {
calculation: {
costRateMode: "resource",
billRateMode: "manual",
},
},
});
expect(result.chapter).toBe("3D Visualization");
expect(result.monthlySpread["2026-03"]).toBe(80);
expect(result.metadata.calculation?.costRateMode).toBe("resource");
});
it("rejects negative hours", () => {
expect(() =>
EstimateDemandLineSchema.parse({ name: "Test", hours: -1 }),
).toThrow();
});
});
describe("ScopeItemSchema", () => {
it("accepts valid scope item", () => {
const result = ScopeItemSchema.parse({
sequenceNo: 1,
scopeType: "shot",
name: "Hero shot exterior",
});
expect(result.sequenceNo).toBe(1);
expect(result.technicalSpec).toEqual({});
expect(result.metadata).toEqual({});
});
it("rejects negative sequenceNo", () => {
expect(() =>
ScopeItemSchema.parse({ sequenceNo: -1, scopeType: "shot", name: "Test" }),
).toThrow();
});
});
describe("ResourceCostSnapshotSchema", () => {
it("accepts valid snapshot", () => {
const result = ResourceCostSnapshotSchema.parse({
displayName: "John Doe",
lcrCents: 6000,
ucrCents: 4000,
});
expect(result.displayName).toBe("John Doe");
expect(result.currency).toBe("EUR");
});
it("rejects negative LCR", () => {
expect(() =>
ResourceCostSnapshotSchema.parse({
displayName: "Test",
lcrCents: -100,
ucrCents: 0,
}),
).toThrow();
});
});
describe("EstimateExportSummarySchema", () => {
it("accepts valid export summary", () => {
const result = EstimateExportSummarySchema.parse({
estimateId: "est-1",
estimateName: "Test Estimate",
versionId: "ver-1",
versionNumber: 1,
versionStatus: EstimateVersionStatus.APPROVED,
baseCurrency: "EUR",
assumptionCount: 5,
scopeItemCount: 10,
demandLineCount: 8,
resourceSnapshotCount: 3,
totalHours: 1200,
totalCostCents: 600_000_00,
totalPriceCents: 900_000_00,
marginCents: 300_000_00,
marginPercent: 33.33,
});
expect(result.marginPercent).toBeCloseTo(33.33);
});
});