864 lines
30 KiB
TypeScript
864 lines
30 KiB
TypeScript
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}.`,
|
|
};
|
|
},
|
|
};
|
|
}
|