refactor(api): extract assistant estimate slice
This commit is contained in:
@@ -6,17 +6,14 @@
|
||||
import { Prisma, ImportBatchStatus, StagedRecordStatus, DispoStagedRecordType, VacationType } from "@capakraken/db";
|
||||
import {
|
||||
CreateAssignmentSchema,
|
||||
type CreateEstimateInput,
|
||||
CreateProjectSchema,
|
||||
CreateResourceSchema,
|
||||
AllocationStatus,
|
||||
EstimateExportFormat,
|
||||
EstimateStatus,
|
||||
type CommentEntityType,
|
||||
COMMENT_ENTITY_TYPE_VALUES,
|
||||
PermissionKey,
|
||||
SystemRole,
|
||||
type UpdateEstimateDraftInput,
|
||||
UpdateProjectSchema,
|
||||
UpdateResourceSchema,
|
||||
} from "@capakraken/shared";
|
||||
@@ -108,6 +105,11 @@ import {
|
||||
notificationInboxToolDefinitions,
|
||||
notificationTaskToolDefinitions,
|
||||
} from "./assistant-tools/notifications-tasks.js";
|
||||
import {
|
||||
createEstimateExecutors,
|
||||
estimateMutationToolDefinitions,
|
||||
estimateReadToolDefinitions,
|
||||
} from "./assistant-tools/estimates.js";
|
||||
import type { ToolContext, ToolDef, ToolExecutor } from "./assistant-tools/shared.js";
|
||||
import { getCommentToolEntityDescription, getCommentToolScopeSentence } from "../lib/comment-entity-registry.js";
|
||||
|
||||
@@ -2503,259 +2505,8 @@ export const TOOL_DEFINITIONS: ToolDef[] = [
|
||||
},
|
||||
|
||||
// ── ESTIMATES ──
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_estimate_detail",
|
||||
description: "Get one estimate via the real estimate router, including versions, demand lines, metrics, assumptions, and linked project data. Controller/manager/admin access and viewCosts required.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID" },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "list_estimate_versions",
|
||||
description: "List estimate versions via the real estimate router, including status, timestamps, and artifact counts. Controller/manager/admin access required.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID" },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_estimate_version_snapshot",
|
||||
description: "Get an estimate version snapshot via the real estimate router, including totals, breakdowns, exports, and resource snapshots. Controller/manager/admin access and viewCosts required.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID" },
|
||||
versionId: { type: "string", description: "Optional explicit version ID. Defaults to the latest version." },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "create_estimate",
|
||||
description: "Create a new estimate via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
projectId: { type: "string", description: "Optional project ID." },
|
||||
projectCode: { type: "string", description: "Optional project short code convenience alias." },
|
||||
name: { type: "string", description: "Estimate name." },
|
||||
opportunityId: { type: "string", description: "Optional opportunity/reference ID." },
|
||||
baseCurrency: { type: "string", description: "Base currency, e.g. EUR." },
|
||||
status: { type: "string", enum: ["DRAFT", "IN_REVIEW", "APPROVED", "ARCHIVED"] },
|
||||
versionLabel: { type: "string", description: "Optional working version label." },
|
||||
versionNotes: { type: "string", description: "Optional working version notes." },
|
||||
assumptions: { type: "array", items: { type: "object" }, description: "Estimate assumptions." },
|
||||
scopeItems: { type: "array", items: { type: "object" }, description: "Estimate scope items." },
|
||||
demandLines: { type: "array", items: { type: "object" }, description: "Estimate demand lines." },
|
||||
resourceSnapshots: { type: "array", items: { type: "object" }, description: "Resource cost snapshots." },
|
||||
metrics: { type: "array", items: { type: "object" }, description: "Optional metric overrides." },
|
||||
},
|
||||
required: ["name"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "clone_estimate",
|
||||
description: "Clone an existing estimate via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
sourceEstimateId: { type: "string", description: "Source estimate ID." },
|
||||
name: { type: "string", description: "Optional cloned estimate name." },
|
||||
projectId: { type: "string", description: "Optional target project ID." },
|
||||
projectCode: { type: "string", description: "Optional target project short code convenience alias." },
|
||||
},
|
||||
required: ["sourceEstimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "update_estimate_draft",
|
||||
description: "Update the working draft of an estimate via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string", description: "Estimate ID." },
|
||||
projectId: { type: "string", description: "Optional linked project ID." },
|
||||
projectCode: { type: "string", description: "Optional linked project short code convenience alias." },
|
||||
name: { type: "string" },
|
||||
opportunityId: { type: "string" },
|
||||
baseCurrency: { type: "string" },
|
||||
status: { type: "string", enum: ["DRAFT", "IN_REVIEW", "APPROVED", "ARCHIVED"] },
|
||||
versionLabel: { type: "string" },
|
||||
versionNotes: { type: "string" },
|
||||
assumptions: { type: "array", items: { type: "object" } },
|
||||
scopeItems: { type: "array", items: { type: "object" } },
|
||||
demandLines: { type: "array", items: { type: "object" } },
|
||||
resourceSnapshots: { type: "array", items: { type: "object" } },
|
||||
metrics: { type: "array", items: { type: "object" } },
|
||||
},
|
||||
required: ["id"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "submit_estimate_version",
|
||||
description: "Submit an estimate working version for review via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
versionId: { type: "string", description: "Optional explicit version ID." },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "approve_estimate_version",
|
||||
description: "Approve a submitted estimate version via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
versionId: { type: "string", description: "Optional explicit version ID." },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "create_estimate_revision",
|
||||
description: "Create a new working revision from the latest locked estimate version via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
sourceVersionId: { type: "string", description: "Optional source version ID." },
|
||||
label: { type: "string", description: "Optional revision label." },
|
||||
notes: { type: "string", description: "Optional revision notes." },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "create_estimate_export",
|
||||
description: "Create an estimate export artifact via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
versionId: { type: "string", description: "Optional explicit version ID." },
|
||||
format: { type: "string", enum: ["XLSX", "CSV", "JSON", "SAP", "MMP"], description: "Export format." },
|
||||
},
|
||||
required: ["estimateId", "format"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "create_estimate_planning_handoff",
|
||||
description: "Create planning allocations from an approved estimate version via the real estimate router. Manager/admin role and manageAllocations permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
versionId: { type: "string", description: "Optional explicit approved version ID." },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "generate_estimate_weekly_phasing",
|
||||
description: "Generate weekly phasing for the working estimate version via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
startDate: { type: "string", description: "Start date in YYYY-MM-DD." },
|
||||
endDate: { type: "string", description: "End date in YYYY-MM-DD." },
|
||||
pattern: { type: "string", enum: ["even", "front_loaded", "back_loaded", "custom"], description: "Distribution pattern." },
|
||||
},
|
||||
required: ["estimateId", "startDate", "endDate"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_estimate_weekly_phasing",
|
||||
description: "Get generated weekly phasing for an estimate via the real estimate router. Controller/manager/admin access required.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_estimate_commercial_terms",
|
||||
description: "Get estimate commercial terms via the real estimate router. Controller/manager/admin access required.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
versionId: { type: "string", description: "Optional explicit version ID." },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "update_estimate_commercial_terms",
|
||||
description: "Update estimate commercial terms on a working version via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
versionId: { type: "string", description: "Optional explicit version ID." },
|
||||
terms: { type: "object", description: "Commercial terms payload." },
|
||||
},
|
||||
required: ["estimateId", "terms"],
|
||||
},
|
||||
},
|
||||
},
|
||||
...estimateReadToolDefinitions,
|
||||
...estimateMutationToolDefinitions,
|
||||
|
||||
// ── ROLES ──
|
||||
...rolesAnalyticsMutationToolDefinitions,
|
||||
@@ -4383,453 +4134,16 @@ const executors = {
|
||||
},
|
||||
|
||||
// ── ESTIMATES ──
|
||||
|
||||
async get_estimate_detail(params: { estimateId: string }, ctx: ToolContext) {
|
||||
assertPermission(ctx, PermissionKey.VIEW_COSTS);
|
||||
const caller = createEstimateCaller(createScopedCallerContext(ctx));
|
||||
try {
|
||||
return await caller.getById({ id: params.estimateId });
|
||||
} catch (error) {
|
||||
const mapped = toAssistantEstimateNotFoundError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async list_estimate_versions(params: { estimateId: string }, ctx: ToolContext) {
|
||||
const caller = createEstimateCaller(createScopedCallerContext(ctx));
|
||||
try {
|
||||
return await caller.listVersions({ estimateId: params.estimateId });
|
||||
} catch (error) {
|
||||
const mapped = toAssistantEstimateNotFoundError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async get_estimate_version_snapshot(params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
}, ctx: ToolContext) {
|
||||
assertPermission(ctx, PermissionKey.VIEW_COSTS);
|
||||
const caller = createEstimateCaller(createScopedCallerContext(ctx));
|
||||
try {
|
||||
return await caller.getVersionSnapshot({
|
||||
estimateId: params.estimateId,
|
||||
...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = toAssistantEstimateNotFoundError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async create_estimate(params: {
|
||||
projectId?: string;
|
||||
projectCode?: string;
|
||||
name: string;
|
||||
opportunityId?: string;
|
||||
baseCurrency?: string;
|
||||
status?: EstimateStatus;
|
||||
versionLabel?: string;
|
||||
versionNotes?: string;
|
||||
assumptions?: CreateEstimateInput["assumptions"];
|
||||
scopeItems?: CreateEstimateInput["scopeItems"];
|
||||
demandLines?: CreateEstimateInput["demandLines"];
|
||||
resourceSnapshots?: CreateEstimateInput["resourceSnapshots"];
|
||||
metrics?: CreateEstimateInput["metrics"];
|
||||
}, ctx: ToolContext) {
|
||||
const caller = createEstimateCaller(createScopedCallerContext(ctx));
|
||||
let projectId = params.projectId;
|
||||
if (!projectId && params.projectCode) {
|
||||
const project = await resolveProjectIdentifier(ctx, params.projectCode);
|
||||
if ("error" in project) {
|
||||
return project;
|
||||
}
|
||||
projectId = project.id;
|
||||
}
|
||||
|
||||
let estimate;
|
||||
try {
|
||||
estimate = await caller.create({
|
||||
name: params.name,
|
||||
...(projectId ? { projectId } : {}),
|
||||
...(params.opportunityId !== undefined ? { opportunityId: params.opportunityId } : {}),
|
||||
...(params.baseCurrency !== undefined ? { baseCurrency: params.baseCurrency } : {}),
|
||||
...(params.status !== undefined ? { status: params.status } : {}),
|
||||
...(params.versionLabel !== undefined ? { versionLabel: params.versionLabel } : {}),
|
||||
...(params.versionNotes !== undefined ? { versionNotes: params.versionNotes } : {}),
|
||||
...(params.assumptions !== undefined ? { assumptions: params.assumptions } : {}),
|
||||
...(params.scopeItems !== undefined ? { scopeItems: params.scopeItems } : {}),
|
||||
...(params.demandLines !== undefined ? { demandLines: params.demandLines } : {}),
|
||||
...(params.resourceSnapshots !== undefined ? { resourceSnapshots: params.resourceSnapshots } : {}),
|
||||
...(params.metrics !== undefined ? { metrics: params.metrics } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = toAssistantEstimateCreationError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
estimate,
|
||||
estimateId: estimate.id,
|
||||
message: `Created estimate "${estimate.name}".`,
|
||||
};
|
||||
},
|
||||
|
||||
async clone_estimate(params: {
|
||||
sourceEstimateId: string;
|
||||
name?: string;
|
||||
projectId?: string;
|
||||
projectCode?: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = createEstimateCaller(createScopedCallerContext(ctx));
|
||||
let projectId = params.projectId;
|
||||
if (!projectId && params.projectCode) {
|
||||
const project = await resolveProjectIdentifier(ctx, params.projectCode);
|
||||
if ("error" in project) {
|
||||
return project;
|
||||
}
|
||||
projectId = project.id;
|
||||
}
|
||||
|
||||
let estimate;
|
||||
try {
|
||||
estimate = await caller.clone({
|
||||
sourceEstimateId: params.sourceEstimateId,
|
||||
...(params.name !== undefined ? { name: params.name } : {}),
|
||||
...(projectId ? { projectId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = toAssistantEstimateMutationError(error, "clone");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
estimate,
|
||||
estimateId: estimate.id,
|
||||
message: `Cloned estimate "${estimate.name}".`,
|
||||
};
|
||||
},
|
||||
|
||||
async update_estimate_draft(params: {
|
||||
id: string;
|
||||
projectId?: string;
|
||||
projectCode?: string;
|
||||
name?: string;
|
||||
opportunityId?: string;
|
||||
baseCurrency?: string;
|
||||
status?: EstimateStatus;
|
||||
versionLabel?: string;
|
||||
versionNotes?: string;
|
||||
assumptions?: UpdateEstimateDraftInput["assumptions"];
|
||||
scopeItems?: UpdateEstimateDraftInput["scopeItems"];
|
||||
demandLines?: UpdateEstimateDraftInput["demandLines"];
|
||||
resourceSnapshots?: UpdateEstimateDraftInput["resourceSnapshots"];
|
||||
metrics?: UpdateEstimateDraftInput["metrics"];
|
||||
}, ctx: ToolContext) {
|
||||
const caller = createEstimateCaller(createScopedCallerContext(ctx));
|
||||
let projectId = params.projectId;
|
||||
if (!projectId && params.projectCode) {
|
||||
const project = await resolveProjectIdentifier(ctx, params.projectCode);
|
||||
if ("error" in project) {
|
||||
return project;
|
||||
}
|
||||
projectId = project.id;
|
||||
}
|
||||
|
||||
let estimate;
|
||||
try {
|
||||
estimate = await caller.updateDraft({
|
||||
id: params.id,
|
||||
...(projectId ? { projectId } : {}),
|
||||
...(params.name !== undefined ? { name: params.name } : {}),
|
||||
...(params.opportunityId !== undefined ? { opportunityId: params.opportunityId } : {}),
|
||||
...(params.baseCurrency !== undefined ? { baseCurrency: params.baseCurrency } : {}),
|
||||
...(params.status !== undefined ? { status: params.status } : {}),
|
||||
...(params.versionLabel !== undefined ? { versionLabel: params.versionLabel } : {}),
|
||||
...(params.versionNotes !== undefined ? { versionNotes: params.versionNotes } : {}),
|
||||
...(params.assumptions !== undefined ? { assumptions: params.assumptions } : {}),
|
||||
...(params.scopeItems !== undefined ? { scopeItems: params.scopeItems } : {}),
|
||||
...(params.demandLines !== undefined ? { demandLines: params.demandLines } : {}),
|
||||
...(params.resourceSnapshots !== undefined ? { resourceSnapshots: params.resourceSnapshots } : {}),
|
||||
...(params.metrics !== undefined ? { metrics: params.metrics } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = toAssistantEstimateMutationError(error, "updateDraft");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
estimate,
|
||||
estimateId: estimate.id,
|
||||
message: `Updated estimate draft "${estimate.name}".`,
|
||||
};
|
||||
},
|
||||
|
||||
async submit_estimate_version(params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = createEstimateCaller(createScopedCallerContext(ctx));
|
||||
let estimate;
|
||||
try {
|
||||
estimate = await caller.submitVersion({
|
||||
estimateId: params.estimateId,
|
||||
...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = toAssistantEstimateMutationError(error, "submitVersion");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
estimate,
|
||||
estimateId: estimate.id,
|
||||
message: `Submitted estimate version for "${estimate.name}".`,
|
||||
};
|
||||
},
|
||||
|
||||
async approve_estimate_version(params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = createEstimateCaller(createScopedCallerContext(ctx));
|
||||
let estimate;
|
||||
try {
|
||||
estimate = await caller.approveVersion({
|
||||
estimateId: params.estimateId,
|
||||
...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = toAssistantEstimateMutationError(error, "approveVersion");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
estimate,
|
||||
estimateId: estimate.id,
|
||||
message: `Approved estimate version for "${estimate.name}".`,
|
||||
};
|
||||
},
|
||||
|
||||
async create_estimate_revision(params: {
|
||||
estimateId: string;
|
||||
sourceVersionId?: string;
|
||||
label?: string;
|
||||
notes?: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = createEstimateCaller(createScopedCallerContext(ctx));
|
||||
let estimate;
|
||||
try {
|
||||
estimate = await caller.createRevision({
|
||||
estimateId: params.estimateId,
|
||||
...(params.sourceVersionId !== undefined ? { sourceVersionId: params.sourceVersionId } : {}),
|
||||
...(params.label !== undefined ? { label: params.label } : {}),
|
||||
...(params.notes !== undefined ? { notes: params.notes } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = toAssistantEstimateMutationError(error, "createRevision");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
estimate,
|
||||
estimateId: estimate.id,
|
||||
message: `Created a new estimate revision for "${estimate.name}".`,
|
||||
};
|
||||
},
|
||||
|
||||
async create_estimate_export(params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
format: EstimateExportFormat;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = createEstimateCaller(createScopedCallerContext(ctx));
|
||||
let estimate;
|
||||
try {
|
||||
estimate = await caller.createExport({
|
||||
estimateId: params.estimateId,
|
||||
format: params.format,
|
||||
...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = toAssistantEstimateMutationError(error, "createExport");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
estimate,
|
||||
estimateId: estimate.id,
|
||||
message: `Created ${params.format} export for estimate "${estimate.name}".`,
|
||||
};
|
||||
},
|
||||
|
||||
async create_estimate_planning_handoff(params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = createEstimateCaller(createScopedCallerContext(ctx));
|
||||
let result;
|
||||
try {
|
||||
result = await caller.createPlanningHandoff({
|
||||
estimateId: params.estimateId,
|
||||
...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = toAssistantEstimateMutationError(error, "createPlanningHandoff");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate", "allocation", "timeline"],
|
||||
success: true,
|
||||
...result,
|
||||
message: `Created planning handoff for estimate ${params.estimateId}.`,
|
||||
};
|
||||
},
|
||||
|
||||
async generate_estimate_weekly_phasing(params: {
|
||||
estimateId: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
pattern?: "even" | "front_loaded" | "back_loaded" | "custom";
|
||||
}, ctx: ToolContext) {
|
||||
const caller = createEstimateCaller(createScopedCallerContext(ctx));
|
||||
let result;
|
||||
try {
|
||||
result = await caller.generateWeeklyPhasing({
|
||||
estimateId: params.estimateId,
|
||||
startDate: params.startDate,
|
||||
endDate: params.endDate,
|
||||
...(params.pattern !== undefined ? { pattern: params.pattern } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = toAssistantEstimateMutationError(error, "generateWeeklyPhasing");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
...result,
|
||||
message: `Generated weekly phasing for estimate ${params.estimateId}.`,
|
||||
};
|
||||
},
|
||||
|
||||
async get_estimate_weekly_phasing(params: {
|
||||
estimateId: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = createEstimateCaller(createScopedCallerContext(ctx));
|
||||
try {
|
||||
return await caller.getWeeklyPhasing({ estimateId: params.estimateId });
|
||||
} catch (error) {
|
||||
const mapped = toAssistantEstimateReadError(error, "weeklyPhasing");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async get_estimate_commercial_terms(params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = createEstimateCaller(createScopedCallerContext(ctx));
|
||||
try {
|
||||
return await caller.getCommercialTerms({
|
||||
estimateId: params.estimateId,
|
||||
...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = toAssistantEstimateReadError(error, "commercialTerms");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async update_estimate_commercial_terms(params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
terms: Record<string, unknown>;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = createEstimateCaller(createScopedCallerContext(ctx));
|
||||
let result;
|
||||
try {
|
||||
result = await caller.updateCommercialTerms({
|
||||
estimateId: params.estimateId,
|
||||
terms: params.terms,
|
||||
...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = toAssistantEstimateMutationError(error, "updateCommercialTerms");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
...result,
|
||||
message: `Updated commercial terms for estimate ${params.estimateId}.`,
|
||||
};
|
||||
},
|
||||
...createEstimateExecutors({
|
||||
assertPermission,
|
||||
createEstimateCaller,
|
||||
createScopedCallerContext,
|
||||
resolveProjectIdentifier,
|
||||
toAssistantEstimateNotFoundError,
|
||||
toAssistantEstimateReadError,
|
||||
toAssistantEstimateCreationError,
|
||||
toAssistantEstimateMutationError,
|
||||
}),
|
||||
|
||||
// ── ROLES ──
|
||||
|
||||
|
||||
@@ -0,0 +1,863 @@
|
||||
import type {
|
||||
CreateEstimateInput,
|
||||
EstimateExportFormat,
|
||||
EstimateStatus,
|
||||
PermissionKey,
|
||||
UpdateEstimateDraftInput,
|
||||
} from "@capakraken/shared";
|
||||
import type { TRPCContext } from "../../trpc.js";
|
||||
import type { ToolContext, ToolDef, ToolExecutor } from "./shared.js";
|
||||
|
||||
type AssistantToolErrorResult = { error: string };
|
||||
|
||||
type EstimateMutationAction =
|
||||
| "clone"
|
||||
| "updateDraft"
|
||||
| "submitVersion"
|
||||
| "approveVersion"
|
||||
| "createRevision"
|
||||
| "createExport"
|
||||
| "createPlanningHandoff"
|
||||
| "generateWeeklyPhasing"
|
||||
| "updateCommercialTerms";
|
||||
|
||||
type WeeklyPhasingPattern = "even" | "front_loaded" | "back_loaded" | "custom";
|
||||
|
||||
type ResolvedProject = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
type EstimateRecord = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type EstimateToolsDeps = {
|
||||
assertPermission: (ctx: ToolContext, perm: PermissionKey) => void;
|
||||
createEstimateCaller: (ctx: TRPCContext) => {
|
||||
getById: (params: { id: string }) => Promise<unknown>;
|
||||
listVersions: (params: { estimateId: string }) => Promise<unknown>;
|
||||
getVersionSnapshot: (params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
}) => Promise<unknown>;
|
||||
create: (params: {
|
||||
name: string;
|
||||
projectId?: string;
|
||||
opportunityId?: string;
|
||||
baseCurrency?: string;
|
||||
status?: EstimateStatus;
|
||||
versionLabel?: string;
|
||||
versionNotes?: string;
|
||||
assumptions?: CreateEstimateInput["assumptions"];
|
||||
scopeItems?: CreateEstimateInput["scopeItems"];
|
||||
demandLines?: CreateEstimateInput["demandLines"];
|
||||
resourceSnapshots?: CreateEstimateInput["resourceSnapshots"];
|
||||
metrics?: CreateEstimateInput["metrics"];
|
||||
}) => Promise<EstimateRecord>;
|
||||
clone: (params: {
|
||||
sourceEstimateId: string;
|
||||
name?: string;
|
||||
projectId?: string;
|
||||
}) => Promise<EstimateRecord>;
|
||||
updateDraft: (params: {
|
||||
id: string;
|
||||
projectId?: string;
|
||||
name?: string;
|
||||
opportunityId?: string;
|
||||
baseCurrency?: string;
|
||||
status?: EstimateStatus;
|
||||
versionLabel?: string;
|
||||
versionNotes?: string;
|
||||
assumptions?: UpdateEstimateDraftInput["assumptions"];
|
||||
scopeItems?: UpdateEstimateDraftInput["scopeItems"];
|
||||
demandLines?: UpdateEstimateDraftInput["demandLines"];
|
||||
resourceSnapshots?: UpdateEstimateDraftInput["resourceSnapshots"];
|
||||
metrics?: UpdateEstimateDraftInput["metrics"];
|
||||
}) => Promise<EstimateRecord>;
|
||||
submitVersion: (params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
}) => Promise<EstimateRecord>;
|
||||
approveVersion: (params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
}) => Promise<EstimateRecord>;
|
||||
createRevision: (params: {
|
||||
estimateId: string;
|
||||
sourceVersionId?: string;
|
||||
label?: string;
|
||||
notes?: string;
|
||||
}) => Promise<EstimateRecord>;
|
||||
createExport: (params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
format: EstimateExportFormat;
|
||||
}) => Promise<EstimateRecord>;
|
||||
createPlanningHandoff: (params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
}) => Promise<object>;
|
||||
generateWeeklyPhasing: (params: {
|
||||
estimateId: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
pattern?: WeeklyPhasingPattern;
|
||||
}) => Promise<object>;
|
||||
getWeeklyPhasing: (params: { estimateId: string }) => Promise<unknown>;
|
||||
getCommercialTerms: (params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
}) => Promise<unknown>;
|
||||
updateCommercialTerms: (params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
terms: Record<string, unknown>;
|
||||
}) => Promise<object>;
|
||||
};
|
||||
createScopedCallerContext: (ctx: ToolContext) => TRPCContext;
|
||||
resolveProjectIdentifier: (
|
||||
ctx: ToolContext,
|
||||
identifier: string,
|
||||
) => Promise<ResolvedProject | AssistantToolErrorResult>;
|
||||
toAssistantEstimateNotFoundError: (
|
||||
error: unknown,
|
||||
) => AssistantToolErrorResult | null;
|
||||
toAssistantEstimateReadError: (
|
||||
error: unknown,
|
||||
context: "weeklyPhasing" | "commercialTerms",
|
||||
) => AssistantToolErrorResult | null;
|
||||
toAssistantEstimateCreationError: (
|
||||
error: unknown,
|
||||
) => AssistantToolErrorResult | null;
|
||||
toAssistantEstimateMutationError: (
|
||||
error: unknown,
|
||||
action: EstimateMutationAction,
|
||||
) => AssistantToolErrorResult | null;
|
||||
};
|
||||
|
||||
async function resolveEstimateProjectId(
|
||||
ctx: ToolContext,
|
||||
deps: EstimateToolsDeps,
|
||||
params: { projectId?: string; projectCode?: string },
|
||||
): Promise<string | AssistantToolErrorResult | undefined> {
|
||||
if (params.projectId) {
|
||||
return params.projectId;
|
||||
}
|
||||
if (!params.projectCode) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const project = await deps.resolveProjectIdentifier(ctx, params.projectCode);
|
||||
if ("error" in project) {
|
||||
return project;
|
||||
}
|
||||
return project.id;
|
||||
}
|
||||
|
||||
export const estimateReadToolDefinitions: ToolDef[] = [
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_estimate_detail",
|
||||
description: "Get one estimate via the real estimate router, including versions, demand lines, metrics, assumptions, and linked project data. Controller/manager/admin access and viewCosts required.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID" },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "list_estimate_versions",
|
||||
description: "List estimate versions via the real estimate router, including status, timestamps, and artifact counts. Controller/manager/admin access required.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID" },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_estimate_version_snapshot",
|
||||
description: "Get an estimate version snapshot via the real estimate router, including totals, breakdowns, exports, and resource snapshots. Controller/manager/admin access and viewCosts required.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID" },
|
||||
versionId: { type: "string", description: "Optional explicit version ID. Defaults to the latest version." },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_estimate_weekly_phasing",
|
||||
description: "Get generated weekly phasing for an estimate via the real estimate router. Controller/manager/admin access required.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_estimate_commercial_terms",
|
||||
description: "Get estimate commercial terms via the real estimate router. Controller/manager/admin access required.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
versionId: { type: "string", description: "Optional explicit version ID." },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const estimateMutationToolDefinitions: ToolDef[] = [
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "create_estimate",
|
||||
description: "Create a new estimate via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
projectId: { type: "string", description: "Optional project ID." },
|
||||
projectCode: { type: "string", description: "Optional project short code convenience alias." },
|
||||
name: { type: "string", description: "Estimate name." },
|
||||
opportunityId: { type: "string", description: "Optional opportunity/reference ID." },
|
||||
baseCurrency: { type: "string", description: "Base currency, e.g. EUR." },
|
||||
status: { type: "string", enum: ["DRAFT", "IN_REVIEW", "APPROVED", "ARCHIVED"] },
|
||||
versionLabel: { type: "string", description: "Optional working version label." },
|
||||
versionNotes: { type: "string", description: "Optional working version notes." },
|
||||
assumptions: { type: "array", items: { type: "object" }, description: "Estimate assumptions." },
|
||||
scopeItems: { type: "array", items: { type: "object" }, description: "Estimate scope items." },
|
||||
demandLines: { type: "array", items: { type: "object" }, description: "Estimate demand lines." },
|
||||
resourceSnapshots: { type: "array", items: { type: "object" }, description: "Resource cost snapshots." },
|
||||
metrics: { type: "array", items: { type: "object" }, description: "Optional metric overrides." },
|
||||
},
|
||||
required: ["name"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "clone_estimate",
|
||||
description: "Clone an existing estimate via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
sourceEstimateId: { type: "string", description: "Source estimate ID." },
|
||||
name: { type: "string", description: "Optional cloned estimate name." },
|
||||
projectId: { type: "string", description: "Optional target project ID." },
|
||||
projectCode: { type: "string", description: "Optional target project short code convenience alias." },
|
||||
},
|
||||
required: ["sourceEstimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "update_estimate_draft",
|
||||
description: "Update the working draft of an estimate via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string", description: "Estimate ID." },
|
||||
projectId: { type: "string", description: "Optional linked project ID." },
|
||||
projectCode: { type: "string", description: "Optional linked project short code convenience alias." },
|
||||
name: { type: "string" },
|
||||
opportunityId: { type: "string" },
|
||||
baseCurrency: { type: "string" },
|
||||
status: { type: "string", enum: ["DRAFT", "IN_REVIEW", "APPROVED", "ARCHIVED"] },
|
||||
versionLabel: { type: "string" },
|
||||
versionNotes: { type: "string" },
|
||||
assumptions: { type: "array", items: { type: "object" } },
|
||||
scopeItems: { type: "array", items: { type: "object" } },
|
||||
demandLines: { type: "array", items: { type: "object" } },
|
||||
resourceSnapshots: { type: "array", items: { type: "object" } },
|
||||
metrics: { type: "array", items: { type: "object" } },
|
||||
},
|
||||
required: ["id"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "submit_estimate_version",
|
||||
description: "Submit an estimate working version for review via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
versionId: { type: "string", description: "Optional explicit version ID." },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "approve_estimate_version",
|
||||
description: "Approve a submitted estimate version via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
versionId: { type: "string", description: "Optional explicit version ID." },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "create_estimate_revision",
|
||||
description: "Create a new working revision from the latest locked estimate version via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
sourceVersionId: { type: "string", description: "Optional source version ID." },
|
||||
label: { type: "string", description: "Optional revision label." },
|
||||
notes: { type: "string", description: "Optional revision notes." },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "create_estimate_export",
|
||||
description: "Create an estimate export artifact via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
versionId: { type: "string", description: "Optional explicit version ID." },
|
||||
format: { type: "string", enum: ["XLSX", "CSV", "JSON", "SAP", "MMP"], description: "Export format." },
|
||||
},
|
||||
required: ["estimateId", "format"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "create_estimate_planning_handoff",
|
||||
description: "Create planning allocations from an approved estimate version via the real estimate router. Manager/admin role and manageAllocations permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
versionId: { type: "string", description: "Optional explicit approved version ID." },
|
||||
},
|
||||
required: ["estimateId"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "generate_estimate_weekly_phasing",
|
||||
description: "Generate weekly phasing for the working estimate version via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
startDate: { type: "string", description: "Start date in YYYY-MM-DD." },
|
||||
endDate: { type: "string", description: "End date in YYYY-MM-DD." },
|
||||
pattern: { type: "string", enum: ["even", "front_loaded", "back_loaded", "custom"], description: "Distribution pattern." },
|
||||
},
|
||||
required: ["estimateId", "startDate", "endDate"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "update_estimate_commercial_terms",
|
||||
description: "Update estimate commercial terms on a working version via the real estimate router. Manager/admin role and manageProjects permission required. Always confirm first.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
estimateId: { type: "string", description: "Estimate ID." },
|
||||
versionId: { type: "string", description: "Optional explicit version ID." },
|
||||
terms: { type: "object", description: "Commercial terms payload." },
|
||||
},
|
||||
required: ["estimateId", "terms"],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function createEstimateExecutors(
|
||||
deps: EstimateToolsDeps,
|
||||
): Record<string, ToolExecutor> {
|
||||
return {
|
||||
async get_estimate_detail(params: { estimateId: string }, ctx: ToolContext) {
|
||||
deps.assertPermission(ctx, "viewCosts");
|
||||
const caller = deps.createEstimateCaller(deps.createScopedCallerContext(ctx));
|
||||
try {
|
||||
return await caller.getById({ id: params.estimateId });
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantEstimateNotFoundError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async list_estimate_versions(params: { estimateId: string }, ctx: ToolContext) {
|
||||
const caller = deps.createEstimateCaller(deps.createScopedCallerContext(ctx));
|
||||
try {
|
||||
return await caller.listVersions({ estimateId: params.estimateId });
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantEstimateNotFoundError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async get_estimate_version_snapshot(params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
}, ctx: ToolContext) {
|
||||
deps.assertPermission(ctx, "viewCosts");
|
||||
const caller = deps.createEstimateCaller(deps.createScopedCallerContext(ctx));
|
||||
try {
|
||||
return await caller.getVersionSnapshot({
|
||||
estimateId: params.estimateId,
|
||||
...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantEstimateNotFoundError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async create_estimate(params: {
|
||||
projectId?: string;
|
||||
projectCode?: string;
|
||||
name: string;
|
||||
opportunityId?: string;
|
||||
baseCurrency?: string;
|
||||
status?: EstimateStatus;
|
||||
versionLabel?: string;
|
||||
versionNotes?: string;
|
||||
assumptions?: CreateEstimateInput["assumptions"];
|
||||
scopeItems?: CreateEstimateInput["scopeItems"];
|
||||
demandLines?: CreateEstimateInput["demandLines"];
|
||||
resourceSnapshots?: CreateEstimateInput["resourceSnapshots"];
|
||||
metrics?: CreateEstimateInput["metrics"];
|
||||
}, ctx: ToolContext) {
|
||||
const caller = deps.createEstimateCaller(deps.createScopedCallerContext(ctx));
|
||||
const projectId = await resolveEstimateProjectId(ctx, deps, params);
|
||||
if (projectId && typeof projectId === "object" && "error" in projectId) {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
let estimate;
|
||||
try {
|
||||
estimate = await caller.create({
|
||||
name: params.name,
|
||||
...(typeof projectId === "string" ? { projectId } : {}),
|
||||
...(params.opportunityId !== undefined ? { opportunityId: params.opportunityId } : {}),
|
||||
...(params.baseCurrency !== undefined ? { baseCurrency: params.baseCurrency } : {}),
|
||||
...(params.status !== undefined ? { status: params.status } : {}),
|
||||
...(params.versionLabel !== undefined ? { versionLabel: params.versionLabel } : {}),
|
||||
...(params.versionNotes !== undefined ? { versionNotes: params.versionNotes } : {}),
|
||||
...(params.assumptions !== undefined ? { assumptions: params.assumptions } : {}),
|
||||
...(params.scopeItems !== undefined ? { scopeItems: params.scopeItems } : {}),
|
||||
...(params.demandLines !== undefined ? { demandLines: params.demandLines } : {}),
|
||||
...(params.resourceSnapshots !== undefined ? { resourceSnapshots: params.resourceSnapshots } : {}),
|
||||
...(params.metrics !== undefined ? { metrics: params.metrics } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantEstimateCreationError(error);
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
estimate,
|
||||
estimateId: estimate.id,
|
||||
message: `Created estimate "${estimate.name}".`,
|
||||
};
|
||||
},
|
||||
|
||||
async clone_estimate(params: {
|
||||
sourceEstimateId: string;
|
||||
name?: string;
|
||||
projectId?: string;
|
||||
projectCode?: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = deps.createEstimateCaller(deps.createScopedCallerContext(ctx));
|
||||
const projectId = await resolveEstimateProjectId(ctx, deps, params);
|
||||
if (projectId && typeof projectId === "object" && "error" in projectId) {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
let estimate;
|
||||
try {
|
||||
estimate = await caller.clone({
|
||||
sourceEstimateId: params.sourceEstimateId,
|
||||
...(params.name !== undefined ? { name: params.name } : {}),
|
||||
...(typeof projectId === "string" ? { projectId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantEstimateMutationError(error, "clone");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
estimate,
|
||||
estimateId: estimate.id,
|
||||
message: `Cloned estimate "${estimate.name}".`,
|
||||
};
|
||||
},
|
||||
|
||||
async update_estimate_draft(params: {
|
||||
id: string;
|
||||
projectId?: string;
|
||||
projectCode?: string;
|
||||
name?: string;
|
||||
opportunityId?: string;
|
||||
baseCurrency?: string;
|
||||
status?: EstimateStatus;
|
||||
versionLabel?: string;
|
||||
versionNotes?: string;
|
||||
assumptions?: UpdateEstimateDraftInput["assumptions"];
|
||||
scopeItems?: UpdateEstimateDraftInput["scopeItems"];
|
||||
demandLines?: UpdateEstimateDraftInput["demandLines"];
|
||||
resourceSnapshots?: UpdateEstimateDraftInput["resourceSnapshots"];
|
||||
metrics?: UpdateEstimateDraftInput["metrics"];
|
||||
}, ctx: ToolContext) {
|
||||
const caller = deps.createEstimateCaller(deps.createScopedCallerContext(ctx));
|
||||
const projectId = await resolveEstimateProjectId(ctx, deps, params);
|
||||
if (projectId && typeof projectId === "object" && "error" in projectId) {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
let estimate;
|
||||
try {
|
||||
estimate = await caller.updateDraft({
|
||||
id: params.id,
|
||||
...(typeof projectId === "string" ? { projectId } : {}),
|
||||
...(params.name !== undefined ? { name: params.name } : {}),
|
||||
...(params.opportunityId !== undefined ? { opportunityId: params.opportunityId } : {}),
|
||||
...(params.baseCurrency !== undefined ? { baseCurrency: params.baseCurrency } : {}),
|
||||
...(params.status !== undefined ? { status: params.status } : {}),
|
||||
...(params.versionLabel !== undefined ? { versionLabel: params.versionLabel } : {}),
|
||||
...(params.versionNotes !== undefined ? { versionNotes: params.versionNotes } : {}),
|
||||
...(params.assumptions !== undefined ? { assumptions: params.assumptions } : {}),
|
||||
...(params.scopeItems !== undefined ? { scopeItems: params.scopeItems } : {}),
|
||||
...(params.demandLines !== undefined ? { demandLines: params.demandLines } : {}),
|
||||
...(params.resourceSnapshots !== undefined ? { resourceSnapshots: params.resourceSnapshots } : {}),
|
||||
...(params.metrics !== undefined ? { metrics: params.metrics } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantEstimateMutationError(error, "updateDraft");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
estimate,
|
||||
estimateId: estimate.id,
|
||||
message: `Updated estimate draft "${estimate.name}".`,
|
||||
};
|
||||
},
|
||||
|
||||
async submit_estimate_version(params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = deps.createEstimateCaller(deps.createScopedCallerContext(ctx));
|
||||
let estimate;
|
||||
try {
|
||||
estimate = await caller.submitVersion({
|
||||
estimateId: params.estimateId,
|
||||
...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantEstimateMutationError(error, "submitVersion");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
estimate,
|
||||
estimateId: estimate.id,
|
||||
message: `Submitted estimate version for "${estimate.name}".`,
|
||||
};
|
||||
},
|
||||
|
||||
async approve_estimate_version(params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = deps.createEstimateCaller(deps.createScopedCallerContext(ctx));
|
||||
let estimate;
|
||||
try {
|
||||
estimate = await caller.approveVersion({
|
||||
estimateId: params.estimateId,
|
||||
...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantEstimateMutationError(error, "approveVersion");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
estimate,
|
||||
estimateId: estimate.id,
|
||||
message: `Approved estimate version for "${estimate.name}".`,
|
||||
};
|
||||
},
|
||||
|
||||
async create_estimate_revision(params: {
|
||||
estimateId: string;
|
||||
sourceVersionId?: string;
|
||||
label?: string;
|
||||
notes?: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = deps.createEstimateCaller(deps.createScopedCallerContext(ctx));
|
||||
let estimate;
|
||||
try {
|
||||
estimate = await caller.createRevision({
|
||||
estimateId: params.estimateId,
|
||||
...(params.sourceVersionId !== undefined ? { sourceVersionId: params.sourceVersionId } : {}),
|
||||
...(params.label !== undefined ? { label: params.label } : {}),
|
||||
...(params.notes !== undefined ? { notes: params.notes } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantEstimateMutationError(error, "createRevision");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
estimate,
|
||||
estimateId: estimate.id,
|
||||
message: `Created a new estimate revision for "${estimate.name}".`,
|
||||
};
|
||||
},
|
||||
|
||||
async create_estimate_export(params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
format: EstimateExportFormat;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = deps.createEstimateCaller(deps.createScopedCallerContext(ctx));
|
||||
let estimate;
|
||||
try {
|
||||
estimate = await caller.createExport({
|
||||
estimateId: params.estimateId,
|
||||
format: params.format,
|
||||
...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantEstimateMutationError(error, "createExport");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
estimate,
|
||||
estimateId: estimate.id,
|
||||
message: `Created ${params.format} export for estimate "${estimate.name}".`,
|
||||
};
|
||||
},
|
||||
|
||||
async create_estimate_planning_handoff(params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = deps.createEstimateCaller(deps.createScopedCallerContext(ctx));
|
||||
let result;
|
||||
try {
|
||||
result = await caller.createPlanningHandoff({
|
||||
estimateId: params.estimateId,
|
||||
...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantEstimateMutationError(error, "createPlanningHandoff");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate", "allocation", "timeline"],
|
||||
success: true,
|
||||
...result,
|
||||
message: `Created planning handoff for estimate ${params.estimateId}.`,
|
||||
};
|
||||
},
|
||||
|
||||
async generate_estimate_weekly_phasing(params: {
|
||||
estimateId: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
pattern?: WeeklyPhasingPattern;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = deps.createEstimateCaller(deps.createScopedCallerContext(ctx));
|
||||
let result;
|
||||
try {
|
||||
result = await caller.generateWeeklyPhasing({
|
||||
estimateId: params.estimateId,
|
||||
startDate: params.startDate,
|
||||
endDate: params.endDate,
|
||||
...(params.pattern !== undefined ? { pattern: params.pattern } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantEstimateMutationError(error, "generateWeeklyPhasing");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
...result,
|
||||
message: `Generated weekly phasing for estimate ${params.estimateId}.`,
|
||||
};
|
||||
},
|
||||
|
||||
async get_estimate_weekly_phasing(params: {
|
||||
estimateId: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = deps.createEstimateCaller(deps.createScopedCallerContext(ctx));
|
||||
try {
|
||||
return await caller.getWeeklyPhasing({ estimateId: params.estimateId });
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantEstimateReadError(error, "weeklyPhasing");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async get_estimate_commercial_terms(params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = deps.createEstimateCaller(deps.createScopedCallerContext(ctx));
|
||||
try {
|
||||
return await caller.getCommercialTerms({
|
||||
estimateId: params.estimateId,
|
||||
...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantEstimateReadError(error, "commercialTerms");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async update_estimate_commercial_terms(params: {
|
||||
estimateId: string;
|
||||
versionId?: string;
|
||||
terms: Record<string, unknown>;
|
||||
}, ctx: ToolContext) {
|
||||
const caller = deps.createEstimateCaller(deps.createScopedCallerContext(ctx));
|
||||
let result;
|
||||
try {
|
||||
result = await caller.updateCommercialTerms({
|
||||
estimateId: params.estimateId,
|
||||
terms: params.terms,
|
||||
...(params.versionId !== undefined ? { versionId: params.versionId } : {}),
|
||||
});
|
||||
} catch (error) {
|
||||
const mapped = deps.toAssistantEstimateMutationError(error, "updateCommercialTerms");
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
__action: "invalidate",
|
||||
scope: ["estimate"],
|
||||
success: true,
|
||||
...result,
|
||||
message: `Updated commercial terms for estimate ${params.estimateId}.`,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user