refactor(api): extract assistant import export dispo slice
This commit is contained in:
@@ -43,12 +43,13 @@
|
|||||||
- the scenario simulation, project narrative, and rate lookup assistant helpers now live in their own domain module, keeping the remaining controller-side scenario/AI analytics wiring out of the monolithic assistant router without changing the assistant contract
|
- the scenario simulation, project narrative, and rate lookup assistant helpers now live in their own domain module, keeping the remaining controller-side scenario/AI analytics wiring out of the monolithic assistant router without changing the assistant contract
|
||||||
- the comment listing and comment mutation assistant helpers now live in their own domain module, keeping collaboration-side comment flows out of the monolithic assistant router without changing the assistant contract
|
- the comment listing and comment mutation assistant helpers now live in their own domain module, keeping collaboration-side comment flows out of the monolithic assistant router without changing the assistant contract
|
||||||
- the audit-history assistant helpers now live in their own domain module, keeping controller-side change-history reads out of the monolithic assistant router without changing the assistant contract
|
- the audit-history assistant helpers now live in their own domain module, keeping controller-side change-history reads out of the monolithic assistant router without changing the assistant contract
|
||||||
|
- the import/export and staged Dispo assistant helpers now live in their own domain module, keeping file-bound export/import and batch-staging orchestration out of the monolithic assistant router without changing the assistant contract
|
||||||
|
|
||||||
## Next Up
|
## Next Up
|
||||||
|
|
||||||
Pin the next structural cleanup on the API side:
|
Pin the next structural cleanup on the API side:
|
||||||
continue splitting `packages/api/src/router/assistant-tools.ts` into domain-oriented tool modules without changing the public tool contract.
|
continue splitting `packages/api/src/router/assistant-tools.ts` into domain-oriented tool modules without changing the public tool contract.
|
||||||
The next clean slice should stay adjacent to the extracted domains and target one cohesive leftover block such as the remaining import/export or staged-dispo helpers still living in the monolithic router.
|
The next clean slice should stay adjacent to the extracted domains and target one cohesive leftover block such as the remaining navigation/search helpers or other small read-only assistant clusters still living in the monolithic router.
|
||||||
|
|
||||||
## Remaining Major Themes
|
## Remaining Major Themes
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Each tool has a JSON schema (for the AI) and an execute function (for the server).
|
* Each tool has a JSON schema (for the AI) and an execute function (for the server).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Prisma, ImportBatchStatus, StagedRecordStatus, DispoStagedRecordType, VacationType } from "@capakraken/db";
|
import { Prisma, VacationType } from "@capakraken/db";
|
||||||
import {
|
import {
|
||||||
CreateAssignmentSchema,
|
CreateAssignmentSchema,
|
||||||
AllocationStatus,
|
AllocationStatus,
|
||||||
@@ -131,6 +131,10 @@ import {
|
|||||||
createScenarioRateAnalysisExecutors,
|
createScenarioRateAnalysisExecutors,
|
||||||
scenarioRateAnalysisToolDefinitions,
|
scenarioRateAnalysisToolDefinitions,
|
||||||
} from "./assistant-tools/scenario-rate-analysis.js";
|
} from "./assistant-tools/scenario-rate-analysis.js";
|
||||||
|
import {
|
||||||
|
createImportExportDispoExecutors,
|
||||||
|
importExportDispoToolDefinitions,
|
||||||
|
} from "./assistant-tools/import-export-dispo.js";
|
||||||
import {
|
import {
|
||||||
commentMutationToolDefinitions,
|
commentMutationToolDefinitions,
|
||||||
commentReadToolDefinitions,
|
commentReadToolDefinitions,
|
||||||
@@ -430,24 +434,6 @@ const LEGACY_MONOLITHIC_TOOL_ACCESS: Partial<Record<string, ToolAccessRequiremen
|
|||||||
delete_project: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
|
delete_project: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
|
||||||
generate_project_cover: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
|
generate_project_cover: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
|
||||||
remove_project_cover: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
|
remove_project_cover: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
|
||||||
export_resources_csv: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
|
||||||
export_projects_csv: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
|
||||||
import_csv_data: {
|
|
||||||
requiredPermissions: [PermissionKey.IMPORT_DATA],
|
|
||||||
allowedSystemRoles: [...MANAGER_ASSISTANT_ROLES],
|
|
||||||
},
|
|
||||||
list_dispo_import_batches: { allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES] },
|
|
||||||
get_dispo_import_batch: { allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES] },
|
|
||||||
stage_dispo_import_batch: { allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES] },
|
|
||||||
validate_dispo_import_batch: { allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES] },
|
|
||||||
cancel_dispo_import_batch: { allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES] },
|
|
||||||
list_dispo_staged_resources: { allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES] },
|
|
||||||
list_dispo_staged_projects: { allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES] },
|
|
||||||
list_dispo_staged_assignments: { allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES] },
|
|
||||||
list_dispo_staged_vacations: { allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES] },
|
|
||||||
list_dispo_staged_unresolved_records: { allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES] },
|
|
||||||
resolve_dispo_staged_record: { allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES] },
|
|
||||||
commit_dispo_import_batch: { allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES] },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ASSISTANT_VACATION_REQUEST_TYPES = [
|
const ASSISTANT_VACATION_REQUEST_TYPES = [
|
||||||
@@ -2328,252 +2314,7 @@ export const TOOL_DEFINITIONS: ToolDef[] = withToolAccess([
|
|||||||
...scenarioRateAnalysisToolDefinitions,
|
...scenarioRateAnalysisToolDefinitions,
|
||||||
...commentMutationToolDefinitions,
|
...commentMutationToolDefinitions,
|
||||||
...auditHistoryToolDefinitions,
|
...auditHistoryToolDefinitions,
|
||||||
{
|
...importExportDispoToolDefinitions,
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "export_resources_csv",
|
|
||||||
description: "Export the current active resource list as CSV via the real import/export router. Controller/manager/admin roles only.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "export_projects_csv",
|
|
||||||
description: "Export the current project list as CSV via the real import/export router. Controller/manager/admin roles only.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "import_csv_data",
|
|
||||||
description: "Import CSV-style row data for resources, projects, or allocations via the real import/export router. Requires manager/admin, importData permission, and confirmation.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
entityType: { type: "string", enum: ["resources", "projects", "allocations"], description: "Import target entity type." },
|
|
||||||
rows: {
|
|
||||||
type: "array",
|
|
||||||
description: "CSV rows already parsed to key/value objects.",
|
|
||||||
items: {
|
|
||||||
type: "object",
|
|
||||||
additionalProperties: { type: "string" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dryRun: { type: "boolean", description: "Validate only without persisting changes. Default: true." },
|
|
||||||
},
|
|
||||||
required: ["entityType", "rows"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "list_dispo_import_batches",
|
|
||||||
description: "List Dispo import batches with pagination and optional status filter via the real dispo router. Admin role required.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
status: { type: "string", description: "Optional batch status filter." },
|
|
||||||
limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
|
|
||||||
cursor: { type: "string", description: "Optional pagination cursor." },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "get_dispo_import_batch",
|
|
||||||
description: "Get one Dispo import batch including staged record counters via the real dispo router. Admin role required.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
id: { type: "string", description: "Import batch ID." },
|
|
||||||
},
|
|
||||||
required: ["id"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "stage_dispo_import_batch",
|
|
||||||
description: "Stage a Dispo import batch via the real dispo router. Admin role required. Always confirm first.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
planningWorkbookPath: { type: "string", description: "Filesystem path to the planning workbook." },
|
|
||||||
referenceWorkbookPath: { type: "string", description: "Filesystem path to the reference workbook." },
|
|
||||||
chargeabilityWorkbookPath: { type: "string", description: "Filesystem path to the chargeability workbook." },
|
|
||||||
costWorkbookPath: { type: "string", description: "Optional filesystem path to the cost workbook." },
|
|
||||||
rosterWorkbookPath: { type: "string", description: "Optional filesystem path to the roster workbook." },
|
|
||||||
notes: { type: "string", description: "Optional import notes." },
|
|
||||||
},
|
|
||||||
required: ["planningWorkbookPath", "referenceWorkbookPath", "chargeabilityWorkbookPath"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "validate_dispo_import_batch",
|
|
||||||
description: "Validate a Dispo import batch readiness check via the real dispo router without committing anything. Admin role required.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
planningWorkbookPath: { type: "string", description: "Filesystem path to the planning workbook." },
|
|
||||||
referenceWorkbookPath: { type: "string", description: "Filesystem path to the reference workbook." },
|
|
||||||
chargeabilityWorkbookPath: { type: "string", description: "Filesystem path to the chargeability workbook." },
|
|
||||||
costWorkbookPath: { type: "string", description: "Optional filesystem path to the cost workbook." },
|
|
||||||
rosterWorkbookPath: { type: "string", description: "Optional filesystem path to the roster workbook." },
|
|
||||||
importBatchId: { type: "string", description: "Optional existing staged import batch ID." },
|
|
||||||
notes: { type: "string", description: "Optional import notes." },
|
|
||||||
},
|
|
||||||
required: ["planningWorkbookPath", "referenceWorkbookPath", "chargeabilityWorkbookPath"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "cancel_dispo_import_batch",
|
|
||||||
description: "Cancel a staged Dispo import batch via the real dispo router. Admin role required. Always confirm first.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
id: { type: "string", description: "Import batch ID." },
|
|
||||||
},
|
|
||||||
required: ["id"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "list_dispo_staged_resources",
|
|
||||||
description: "List staged Dispo resources for one import batch via the real dispo router. Admin role required.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
importBatchId: { type: "string", description: "Import batch ID." },
|
|
||||||
status: { type: "string", description: "Optional staged record status filter." },
|
|
||||||
limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
|
|
||||||
cursor: { type: "string", description: "Optional pagination cursor." },
|
|
||||||
},
|
|
||||||
required: ["importBatchId"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "list_dispo_staged_projects",
|
|
||||||
description: "List staged Dispo projects for one import batch via the real dispo router. Admin role required.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
importBatchId: { type: "string", description: "Import batch ID." },
|
|
||||||
status: { type: "string", description: "Optional staged record status filter." },
|
|
||||||
isTbd: { type: "boolean", description: "Optional TBD-project filter." },
|
|
||||||
limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
|
|
||||||
cursor: { type: "string", description: "Optional pagination cursor." },
|
|
||||||
},
|
|
||||||
required: ["importBatchId"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "list_dispo_staged_assignments",
|
|
||||||
description: "List staged Dispo assignments for one import batch via the real dispo router. Admin role required.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
importBatchId: { type: "string", description: "Import batch ID." },
|
|
||||||
status: { type: "string", description: "Optional staged record status filter." },
|
|
||||||
resourceExternalId: { type: "string", description: "Optional resource external ID filter." },
|
|
||||||
limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
|
|
||||||
cursor: { type: "string", description: "Optional pagination cursor." },
|
|
||||||
},
|
|
||||||
required: ["importBatchId"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "list_dispo_staged_vacations",
|
|
||||||
description: "List staged Dispo vacations for one import batch via the real dispo router. Admin role required.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
importBatchId: { type: "string", description: "Import batch ID." },
|
|
||||||
resourceExternalId: { type: "string", description: "Optional resource external ID filter." },
|
|
||||||
limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
|
|
||||||
cursor: { type: "string", description: "Optional pagination cursor." },
|
|
||||||
},
|
|
||||||
required: ["importBatchId"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "list_dispo_staged_unresolved_records",
|
|
||||||
description: "List staged unresolved Dispo records for one import batch via the real dispo router. Admin role required.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
importBatchId: { type: "string", description: "Import batch ID." },
|
|
||||||
recordType: { type: "string", description: "Optional unresolved record type filter." },
|
|
||||||
limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
|
|
||||||
cursor: { type: "string", description: "Optional pagination cursor." },
|
|
||||||
},
|
|
||||||
required: ["importBatchId"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "resolve_dispo_staged_record",
|
|
||||||
description: "Resolve one staged Dispo record via the real dispo router. Admin role required. Always confirm first.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
id: { type: "string", description: "Staged record ID." },
|
|
||||||
recordType: { type: "string", description: "Staged record type." },
|
|
||||||
action: { type: "string", enum: ["APPROVE", "REJECT", "SKIP"], description: "Resolution action." },
|
|
||||||
},
|
|
||||||
required: ["id", "recordType", "action"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "commit_dispo_import_batch",
|
|
||||||
description: "Commit a staged Dispo import batch via the real dispo router. Admin role required. Always confirm first.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
importBatchId: { type: "string", description: "Import batch ID." },
|
|
||||||
allowTbdUnresolved: { type: "boolean", description: "Allow unresolved TBD projects during commit." },
|
|
||||||
importTbdProjects: { type: "boolean", description: "Whether TBD projects should be imported." },
|
|
||||||
},
|
|
||||||
required: ["importBatchId"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
...settingsAdminToolDefinitions,
|
...settingsAdminToolDefinitions,
|
||||||
], LEGACY_MONOLITHIC_TOOL_ACCESS);
|
], LEGACY_MONOLITHIC_TOOL_ACCESS);
|
||||||
|
|
||||||
@@ -3206,219 +2947,13 @@ const executors = {
|
|||||||
toAssistantNotificationCreationError,
|
toAssistantNotificationCreationError,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
async export_resources_csv(_params: Record<string, never>, ctx: ToolContext) {
|
...createImportExportDispoExecutors({
|
||||||
const caller = createImportExportCaller(createScopedCallerContext(ctx));
|
assertPermission,
|
||||||
const csv = await caller.exportResourcesCSV();
|
createImportExportCaller,
|
||||||
return {
|
createDispoCaller,
|
||||||
format: "csv",
|
createScopedCallerContext,
|
||||||
lineCount: csv.length === 0 ? 0 : csv.split("\n").length,
|
toAssistantDispoImportBatchNotFoundError,
|
||||||
csv,
|
}),
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
async export_projects_csv(_params: Record<string, never>, ctx: ToolContext) {
|
|
||||||
const caller = createImportExportCaller(createScopedCallerContext(ctx));
|
|
||||||
const csv = await caller.exportProjectsCSV();
|
|
||||||
return {
|
|
||||||
format: "csv",
|
|
||||||
lineCount: csv.length === 0 ? 0 : csv.split("\n").length,
|
|
||||||
csv,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
async import_csv_data(params: {
|
|
||||||
entityType: "resources" | "projects" | "allocations";
|
|
||||||
rows: Array<Record<string, string>>;
|
|
||||||
dryRun?: boolean;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
assertPermission(ctx, PermissionKey.IMPORT_DATA);
|
|
||||||
const caller = createImportExportCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.importCSV({
|
|
||||||
entityType: params.entityType,
|
|
||||||
rows: params.rows,
|
|
||||||
dryRun: params.dryRun ?? true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async list_dispo_import_batches(params: {
|
|
||||||
status?: ImportBatchStatus;
|
|
||||||
limit?: number;
|
|
||||||
cursor?: string;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
const caller = createDispoCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.listImportBatches({
|
|
||||||
...(params.status ? { status: params.status } : {}),
|
|
||||||
...(params.cursor ? { cursor: params.cursor } : {}),
|
|
||||||
...(params.limit !== undefined ? { limit: Math.min(Math.max(params.limit, 1), 200) } : {}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async get_dispo_import_batch(params: {
|
|
||||||
id: string;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
const caller = createDispoCaller(createScopedCallerContext(ctx));
|
|
||||||
try {
|
|
||||||
return await caller.getImportBatch({ id: params.id });
|
|
||||||
} catch (error) {
|
|
||||||
const mapped = toAssistantDispoImportBatchNotFoundError(error);
|
|
||||||
if (mapped) {
|
|
||||||
return mapped;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async stage_dispo_import_batch(params: {
|
|
||||||
chargeabilityWorkbookPath: string;
|
|
||||||
costWorkbookPath?: string;
|
|
||||||
notes?: string | null;
|
|
||||||
planningWorkbookPath: string;
|
|
||||||
referenceWorkbookPath: string;
|
|
||||||
rosterWorkbookPath?: string;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
const caller = createDispoCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.stageImportBatch({
|
|
||||||
chargeabilityWorkbookPath: params.chargeabilityWorkbookPath,
|
|
||||||
planningWorkbookPath: params.planningWorkbookPath,
|
|
||||||
referenceWorkbookPath: params.referenceWorkbookPath,
|
|
||||||
...(params.costWorkbookPath !== undefined ? { costWorkbookPath: params.costWorkbookPath } : {}),
|
|
||||||
...(params.notes !== undefined ? { notes: params.notes } : {}),
|
|
||||||
...(params.rosterWorkbookPath !== undefined ? { rosterWorkbookPath: params.rosterWorkbookPath } : {}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async validate_dispo_import_batch(params: {
|
|
||||||
chargeabilityWorkbookPath: string;
|
|
||||||
costWorkbookPath?: string;
|
|
||||||
importBatchId?: string;
|
|
||||||
notes?: string | null;
|
|
||||||
planningWorkbookPath: string;
|
|
||||||
referenceWorkbookPath: string;
|
|
||||||
rosterWorkbookPath?: string;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
const caller = createDispoCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.validateImportBatch({
|
|
||||||
chargeabilityWorkbookPath: params.chargeabilityWorkbookPath,
|
|
||||||
planningWorkbookPath: params.planningWorkbookPath,
|
|
||||||
referenceWorkbookPath: params.referenceWorkbookPath,
|
|
||||||
...(params.costWorkbookPath !== undefined ? { costWorkbookPath: params.costWorkbookPath } : {}),
|
|
||||||
...(params.importBatchId !== undefined ? { importBatchId: params.importBatchId } : {}),
|
|
||||||
...(params.notes !== undefined ? { notes: params.notes } : {}),
|
|
||||||
...(params.rosterWorkbookPath !== undefined ? { rosterWorkbookPath: params.rosterWorkbookPath } : {}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async cancel_dispo_import_batch(params: {
|
|
||||||
id: string;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
const caller = createDispoCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.cancelImportBatch({ id: params.id });
|
|
||||||
},
|
|
||||||
|
|
||||||
async list_dispo_staged_resources(params: {
|
|
||||||
importBatchId: string;
|
|
||||||
status?: StagedRecordStatus;
|
|
||||||
limit?: number;
|
|
||||||
cursor?: string;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
const caller = createDispoCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.listStagedResources({
|
|
||||||
importBatchId: params.importBatchId,
|
|
||||||
...(params.status !== undefined ? { status: params.status } : {}),
|
|
||||||
...(params.cursor ? { cursor: params.cursor } : {}),
|
|
||||||
...(params.limit !== undefined ? { limit: Math.min(Math.max(params.limit, 1), 200) } : {}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async list_dispo_staged_projects(params: {
|
|
||||||
importBatchId: string;
|
|
||||||
status?: StagedRecordStatus;
|
|
||||||
isTbd?: boolean;
|
|
||||||
limit?: number;
|
|
||||||
cursor?: string;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
const caller = createDispoCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.listStagedProjects({
|
|
||||||
importBatchId: params.importBatchId,
|
|
||||||
...(params.status !== undefined ? { status: params.status } : {}),
|
|
||||||
...(params.isTbd !== undefined ? { isTbd: params.isTbd } : {}),
|
|
||||||
...(params.cursor ? { cursor: params.cursor } : {}),
|
|
||||||
...(params.limit !== undefined ? { limit: Math.min(Math.max(params.limit, 1), 200) } : {}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async list_dispo_staged_assignments(params: {
|
|
||||||
importBatchId: string;
|
|
||||||
status?: StagedRecordStatus;
|
|
||||||
resourceExternalId?: string;
|
|
||||||
limit?: number;
|
|
||||||
cursor?: string;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
const caller = createDispoCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.listStagedAssignments({
|
|
||||||
importBatchId: params.importBatchId,
|
|
||||||
...(params.status !== undefined ? { status: params.status } : {}),
|
|
||||||
...(params.resourceExternalId !== undefined ? { resourceExternalId: params.resourceExternalId } : {}),
|
|
||||||
...(params.cursor ? { cursor: params.cursor } : {}),
|
|
||||||
...(params.limit !== undefined ? { limit: Math.min(Math.max(params.limit, 1), 200) } : {}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async list_dispo_staged_vacations(params: {
|
|
||||||
importBatchId: string;
|
|
||||||
resourceExternalId?: string;
|
|
||||||
limit?: number;
|
|
||||||
cursor?: string;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
const caller = createDispoCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.listStagedVacations({
|
|
||||||
importBatchId: params.importBatchId,
|
|
||||||
...(params.resourceExternalId !== undefined ? { resourceExternalId: params.resourceExternalId } : {}),
|
|
||||||
...(params.cursor ? { cursor: params.cursor } : {}),
|
|
||||||
...(params.limit !== undefined ? { limit: Math.min(Math.max(params.limit, 1), 200) } : {}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async list_dispo_staged_unresolved_records(params: {
|
|
||||||
importBatchId: string;
|
|
||||||
recordType?: DispoStagedRecordType;
|
|
||||||
limit?: number;
|
|
||||||
cursor?: string;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
const caller = createDispoCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.listStagedUnresolvedRecords({
|
|
||||||
importBatchId: params.importBatchId,
|
|
||||||
...(params.recordType !== undefined ? { recordType: params.recordType } : {}),
|
|
||||||
...(params.cursor ? { cursor: params.cursor } : {}),
|
|
||||||
...(params.limit !== undefined ? { limit: Math.min(Math.max(params.limit, 1), 200) } : {}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async resolve_dispo_staged_record(params: {
|
|
||||||
action: "APPROVE" | "REJECT" | "SKIP";
|
|
||||||
id: string;
|
|
||||||
recordType: DispoStagedRecordType;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
const caller = createDispoCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.resolveStagedRecord({
|
|
||||||
action: params.action,
|
|
||||||
id: params.id,
|
|
||||||
recordType: params.recordType,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async commit_dispo_import_batch(params: {
|
|
||||||
allowTbdUnresolved?: boolean;
|
|
||||||
importBatchId: string;
|
|
||||||
importTbdProjects?: boolean;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
const caller = createDispoCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.commitImportBatch({
|
|
||||||
importBatchId: params.importBatchId,
|
|
||||||
...(params.allowTbdUnresolved !== undefined ? { allowTbdUnresolved: params.allowTbdUnresolved } : {}),
|
|
||||||
...(params.importTbdProjects !== undefined ? { importTbdProjects: params.importTbdProjects } : {}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
...createSettingsAdminExecutors({
|
...createSettingsAdminExecutors({
|
||||||
createSettingsCaller,
|
createSettingsCaller,
|
||||||
|
|||||||
@@ -0,0 +1,613 @@
|
|||||||
|
import { DispoStagedRecordType, ImportBatchStatus, StagedRecordStatus } from "@capakraken/db";
|
||||||
|
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||||
|
import type { TRPCContext } from "../../trpc.js";
|
||||||
|
import { withToolAccess, type ToolContext, type ToolDef, type ToolExecutor } from "./shared.js";
|
||||||
|
|
||||||
|
type ImportExportDispoDeps = {
|
||||||
|
assertPermission: (ctx: ToolContext, perm: PermissionKey) => void;
|
||||||
|
createImportExportCaller: (ctx: TRPCContext) => {
|
||||||
|
exportResourcesCSV: () => Promise<string>;
|
||||||
|
exportProjectsCSV: () => Promise<string>;
|
||||||
|
importCSV: (params: {
|
||||||
|
entityType: "resources" | "projects" | "allocations";
|
||||||
|
rows: Array<Record<string, string>>;
|
||||||
|
dryRun: boolean;
|
||||||
|
}) => Promise<unknown>;
|
||||||
|
};
|
||||||
|
createDispoCaller: (ctx: TRPCContext) => {
|
||||||
|
listImportBatches: (params: {
|
||||||
|
status?: ImportBatchStatus;
|
||||||
|
limit?: number;
|
||||||
|
cursor?: string;
|
||||||
|
}) => Promise<unknown>;
|
||||||
|
getImportBatch: (params: { id: string }) => Promise<unknown>;
|
||||||
|
stageImportBatch: (params: {
|
||||||
|
chargeabilityWorkbookPath: string;
|
||||||
|
costWorkbookPath?: string;
|
||||||
|
notes?: string | null;
|
||||||
|
planningWorkbookPath: string;
|
||||||
|
referenceWorkbookPath: string;
|
||||||
|
rosterWorkbookPath?: string;
|
||||||
|
}) => Promise<unknown>;
|
||||||
|
validateImportBatch: (params: {
|
||||||
|
chargeabilityWorkbookPath: string;
|
||||||
|
costWorkbookPath?: string;
|
||||||
|
importBatchId?: string;
|
||||||
|
notes?: string | null;
|
||||||
|
planningWorkbookPath: string;
|
||||||
|
referenceWorkbookPath: string;
|
||||||
|
rosterWorkbookPath?: string;
|
||||||
|
}) => Promise<unknown>;
|
||||||
|
cancelImportBatch: (params: { id: string }) => Promise<unknown>;
|
||||||
|
listStagedResources: (params: {
|
||||||
|
importBatchId: string;
|
||||||
|
status?: StagedRecordStatus;
|
||||||
|
limit?: number;
|
||||||
|
cursor?: string;
|
||||||
|
}) => Promise<unknown>;
|
||||||
|
listStagedProjects: (params: {
|
||||||
|
importBatchId: string;
|
||||||
|
status?: StagedRecordStatus;
|
||||||
|
isTbd?: boolean;
|
||||||
|
limit?: number;
|
||||||
|
cursor?: string;
|
||||||
|
}) => Promise<unknown>;
|
||||||
|
listStagedAssignments: (params: {
|
||||||
|
importBatchId: string;
|
||||||
|
status?: StagedRecordStatus;
|
||||||
|
resourceExternalId?: string;
|
||||||
|
limit?: number;
|
||||||
|
cursor?: string;
|
||||||
|
}) => Promise<unknown>;
|
||||||
|
listStagedVacations: (params: {
|
||||||
|
importBatchId: string;
|
||||||
|
resourceExternalId?: string;
|
||||||
|
limit?: number;
|
||||||
|
cursor?: string;
|
||||||
|
}) => Promise<unknown>;
|
||||||
|
listStagedUnresolvedRecords: (params: {
|
||||||
|
importBatchId: string;
|
||||||
|
recordType?: DispoStagedRecordType;
|
||||||
|
limit?: number;
|
||||||
|
cursor?: string;
|
||||||
|
}) => Promise<unknown>;
|
||||||
|
resolveStagedRecord: (params: {
|
||||||
|
action: "APPROVE" | "REJECT" | "SKIP";
|
||||||
|
id: string;
|
||||||
|
recordType: DispoStagedRecordType;
|
||||||
|
}) => Promise<unknown>;
|
||||||
|
commitImportBatch: (params: {
|
||||||
|
importBatchId: string;
|
||||||
|
allowTbdUnresolved?: boolean;
|
||||||
|
importTbdProjects?: boolean;
|
||||||
|
}) => Promise<unknown>;
|
||||||
|
};
|
||||||
|
createScopedCallerContext: (ctx: ToolContext) => TRPCContext;
|
||||||
|
toAssistantDispoImportBatchNotFoundError: (error: unknown) => { error: string } | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CONTROLLER_ASSISTANT_ROLES = [SystemRole.ADMIN, SystemRole.MANAGER, SystemRole.CONTROLLER] as const;
|
||||||
|
const MANAGER_ASSISTANT_ROLES = [SystemRole.ADMIN, SystemRole.MANAGER] as const;
|
||||||
|
const ADMIN_ASSISTANT_ROLES = [SystemRole.ADMIN] as const;
|
||||||
|
|
||||||
|
export const importExportDispoToolDefinitions: ToolDef[] = withToolAccess([
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "export_resources_csv",
|
||||||
|
description: "Export the current active resource list as CSV via the real import/export router. Controller/manager/admin roles only.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "export_projects_csv",
|
||||||
|
description: "Export the current project list as CSV via the real import/export router. Controller/manager/admin roles only.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "import_csv_data",
|
||||||
|
description: "Import CSV-style row data for resources, projects, or allocations via the real import/export router. Requires manager/admin, importData permission, and defaults to dry-run.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
entityType: { type: "string", enum: ["resources", "projects", "allocations"], description: "Import target entity type." },
|
||||||
|
rows: {
|
||||||
|
type: "array",
|
||||||
|
description: "CSV rows already parsed to key/value objects.",
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
additionalProperties: { type: "string" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dryRun: { type: "boolean", description: "Validate only without persisting changes. Default: true." },
|
||||||
|
},
|
||||||
|
required: ["entityType", "rows"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "list_dispo_import_batches",
|
||||||
|
description: "List Dispo import batches with pagination and optional status filter via the real dispo router. Admin role required.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
status: { type: "string", description: "Optional batch status filter." },
|
||||||
|
limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
|
||||||
|
cursor: { type: "string", description: "Optional pagination cursor." },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "get_dispo_import_batch",
|
||||||
|
description: "Get one Dispo import batch including staged record counters via the real dispo router. Admin role required.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
id: { type: "string", description: "Import batch ID." },
|
||||||
|
},
|
||||||
|
required: ["id"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "stage_dispo_import_batch",
|
||||||
|
description: "Stage a Dispo import batch via the real dispo router. Admin role required. Always confirm first.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
planningWorkbookPath: { type: "string", description: "Filesystem path to the planning workbook." },
|
||||||
|
referenceWorkbookPath: { type: "string", description: "Filesystem path to the reference workbook." },
|
||||||
|
chargeabilityWorkbookPath: { type: "string", description: "Filesystem path to the chargeability workbook." },
|
||||||
|
costWorkbookPath: { type: "string", description: "Optional filesystem path to the cost workbook." },
|
||||||
|
rosterWorkbookPath: { type: "string", description: "Optional filesystem path to the roster workbook." },
|
||||||
|
notes: { type: "string", description: "Optional import notes." },
|
||||||
|
},
|
||||||
|
required: ["planningWorkbookPath", "referenceWorkbookPath", "chargeabilityWorkbookPath"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "validate_dispo_import_batch",
|
||||||
|
description: "Validate a Dispo import batch readiness check via the real dispo router without committing anything. Admin role required.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
planningWorkbookPath: { type: "string", description: "Filesystem path to the planning workbook." },
|
||||||
|
referenceWorkbookPath: { type: "string", description: "Filesystem path to the reference workbook." },
|
||||||
|
chargeabilityWorkbookPath: { type: "string", description: "Filesystem path to the chargeability workbook." },
|
||||||
|
costWorkbookPath: { type: "string", description: "Optional filesystem path to the cost workbook." },
|
||||||
|
rosterWorkbookPath: { type: "string", description: "Optional filesystem path to the roster workbook." },
|
||||||
|
importBatchId: { type: "string", description: "Optional existing staged import batch ID." },
|
||||||
|
notes: { type: "string", description: "Optional import notes." },
|
||||||
|
},
|
||||||
|
required: ["planningWorkbookPath", "referenceWorkbookPath", "chargeabilityWorkbookPath"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "cancel_dispo_import_batch",
|
||||||
|
description: "Cancel a staged Dispo import batch via the real dispo router. Admin role required. Always confirm first.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
id: { type: "string", description: "Import batch ID." },
|
||||||
|
},
|
||||||
|
required: ["id"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "list_dispo_staged_resources",
|
||||||
|
description: "List staged Dispo resources for one import batch via the real dispo router. Admin role required.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
importBatchId: { type: "string", description: "Import batch ID." },
|
||||||
|
status: { type: "string", description: "Optional staged record status filter." },
|
||||||
|
limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
|
||||||
|
cursor: { type: "string", description: "Optional pagination cursor." },
|
||||||
|
},
|
||||||
|
required: ["importBatchId"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "list_dispo_staged_projects",
|
||||||
|
description: "List staged Dispo projects for one import batch via the real dispo router. Admin role required.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
importBatchId: { type: "string", description: "Import batch ID." },
|
||||||
|
status: { type: "string", description: "Optional staged record status filter." },
|
||||||
|
isTbd: { type: "boolean", description: "Optional TBD-project filter." },
|
||||||
|
limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
|
||||||
|
cursor: { type: "string", description: "Optional pagination cursor." },
|
||||||
|
},
|
||||||
|
required: ["importBatchId"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "list_dispo_staged_assignments",
|
||||||
|
description: "List staged Dispo assignments for one import batch via the real dispo router. Admin role required.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
importBatchId: { type: "string", description: "Import batch ID." },
|
||||||
|
status: { type: "string", description: "Optional staged record status filter." },
|
||||||
|
resourceExternalId: { type: "string", description: "Optional resource external ID filter." },
|
||||||
|
limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
|
||||||
|
cursor: { type: "string", description: "Optional pagination cursor." },
|
||||||
|
},
|
||||||
|
required: ["importBatchId"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "list_dispo_staged_vacations",
|
||||||
|
description: "List staged Dispo vacations for one import batch via the real dispo router. Admin role required.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
importBatchId: { type: "string", description: "Import batch ID." },
|
||||||
|
resourceExternalId: { type: "string", description: "Optional resource external ID filter." },
|
||||||
|
limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
|
||||||
|
cursor: { type: "string", description: "Optional pagination cursor." },
|
||||||
|
},
|
||||||
|
required: ["importBatchId"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "list_dispo_staged_unresolved_records",
|
||||||
|
description: "List staged unresolved Dispo records for one import batch via the real dispo router. Admin role required.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
importBatchId: { type: "string", description: "Import batch ID." },
|
||||||
|
recordType: { type: "string", description: "Optional unresolved record type filter." },
|
||||||
|
limit: { type: "integer", description: "Max results. Default: 50, max: 200." },
|
||||||
|
cursor: { type: "string", description: "Optional pagination cursor." },
|
||||||
|
},
|
||||||
|
required: ["importBatchId"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "resolve_dispo_staged_record",
|
||||||
|
description: "Resolve one staged Dispo record via the real dispo router. Admin role required. Always confirm first.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
id: { type: "string", description: "Staged record ID." },
|
||||||
|
recordType: { type: "string", description: "Staged record type." },
|
||||||
|
action: { type: "string", enum: ["APPROVE", "REJECT", "SKIP"], description: "Resolution action." },
|
||||||
|
},
|
||||||
|
required: ["id", "recordType", "action"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "commit_dispo_import_batch",
|
||||||
|
description: "Commit a staged Dispo import batch via the real dispo router. Admin role required. Always confirm first.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
importBatchId: { type: "string", description: "Import batch ID." },
|
||||||
|
allowTbdUnresolved: { type: "boolean", description: "Allow unresolved TBD projects during commit." },
|
||||||
|
importTbdProjects: { type: "boolean", description: "Whether TBD projects should be imported." },
|
||||||
|
},
|
||||||
|
required: ["importBatchId"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
], {
|
||||||
|
export_resources_csv: {
|
||||||
|
allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES],
|
||||||
|
},
|
||||||
|
export_projects_csv: {
|
||||||
|
allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES],
|
||||||
|
},
|
||||||
|
import_csv_data: {
|
||||||
|
requiredPermissions: [PermissionKey.IMPORT_DATA],
|
||||||
|
allowedSystemRoles: [...MANAGER_ASSISTANT_ROLES],
|
||||||
|
},
|
||||||
|
list_dispo_import_batches: {
|
||||||
|
allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES],
|
||||||
|
},
|
||||||
|
get_dispo_import_batch: {
|
||||||
|
allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES],
|
||||||
|
},
|
||||||
|
stage_dispo_import_batch: {
|
||||||
|
allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES],
|
||||||
|
},
|
||||||
|
validate_dispo_import_batch: {
|
||||||
|
allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES],
|
||||||
|
},
|
||||||
|
cancel_dispo_import_batch: {
|
||||||
|
allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES],
|
||||||
|
},
|
||||||
|
list_dispo_staged_resources: {
|
||||||
|
allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES],
|
||||||
|
},
|
||||||
|
list_dispo_staged_projects: {
|
||||||
|
allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES],
|
||||||
|
},
|
||||||
|
list_dispo_staged_assignments: {
|
||||||
|
allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES],
|
||||||
|
},
|
||||||
|
list_dispo_staged_vacations: {
|
||||||
|
allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES],
|
||||||
|
},
|
||||||
|
list_dispo_staged_unresolved_records: {
|
||||||
|
allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES],
|
||||||
|
},
|
||||||
|
resolve_dispo_staged_record: {
|
||||||
|
allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES],
|
||||||
|
},
|
||||||
|
commit_dispo_import_batch: {
|
||||||
|
allowedSystemRoles: [...ADMIN_ASSISTANT_ROLES],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function clampLimit(limit: number | undefined): number | undefined {
|
||||||
|
return limit !== undefined ? Math.min(Math.max(limit, 1), 200) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createImportExportDispoExecutors(
|
||||||
|
deps: ImportExportDispoDeps,
|
||||||
|
): Record<string, ToolExecutor> {
|
||||||
|
return {
|
||||||
|
async export_resources_csv(_params: Record<string, never>, ctx: ToolContext) {
|
||||||
|
const caller = deps.createImportExportCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
const csv = await caller.exportResourcesCSV();
|
||||||
|
return {
|
||||||
|
format: "csv",
|
||||||
|
lineCount: csv.length === 0 ? 0 : csv.split("\n").length,
|
||||||
|
csv,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async export_projects_csv(_params: Record<string, never>, ctx: ToolContext) {
|
||||||
|
const caller = deps.createImportExportCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
const csv = await caller.exportProjectsCSV();
|
||||||
|
return {
|
||||||
|
format: "csv",
|
||||||
|
lineCount: csv.length === 0 ? 0 : csv.split("\n").length,
|
||||||
|
csv,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async import_csv_data(
|
||||||
|
params: {
|
||||||
|
entityType: "resources" | "projects" | "allocations";
|
||||||
|
rows: Array<Record<string, string>>;
|
||||||
|
dryRun?: boolean;
|
||||||
|
},
|
||||||
|
ctx: ToolContext,
|
||||||
|
) {
|
||||||
|
deps.assertPermission(ctx, PermissionKey.IMPORT_DATA);
|
||||||
|
const caller = deps.createImportExportCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.importCSV({
|
||||||
|
entityType: params.entityType,
|
||||||
|
rows: params.rows,
|
||||||
|
dryRun: params.dryRun ?? true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async list_dispo_import_batches(
|
||||||
|
params: { status?: ImportBatchStatus; limit?: number; cursor?: string },
|
||||||
|
ctx: ToolContext,
|
||||||
|
) {
|
||||||
|
const limit = clampLimit(params.limit);
|
||||||
|
const caller = deps.createDispoCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.listImportBatches({
|
||||||
|
...(params.status ? { status: params.status } : {}),
|
||||||
|
...(params.cursor ? { cursor: params.cursor } : {}),
|
||||||
|
...(limit !== undefined ? { limit } : {}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async get_dispo_import_batch(
|
||||||
|
params: { id: string },
|
||||||
|
ctx: ToolContext,
|
||||||
|
) {
|
||||||
|
const caller = deps.createDispoCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
try {
|
||||||
|
return await caller.getImportBatch({ id: params.id });
|
||||||
|
} catch (error) {
|
||||||
|
const mapped = deps.toAssistantDispoImportBatchNotFoundError(error);
|
||||||
|
if (mapped) {
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async stage_dispo_import_batch(
|
||||||
|
params: {
|
||||||
|
chargeabilityWorkbookPath: string;
|
||||||
|
costWorkbookPath?: string;
|
||||||
|
notes?: string | null;
|
||||||
|
planningWorkbookPath: string;
|
||||||
|
referenceWorkbookPath: string;
|
||||||
|
rosterWorkbookPath?: string;
|
||||||
|
},
|
||||||
|
ctx: ToolContext,
|
||||||
|
) {
|
||||||
|
const caller = deps.createDispoCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.stageImportBatch({
|
||||||
|
chargeabilityWorkbookPath: params.chargeabilityWorkbookPath,
|
||||||
|
planningWorkbookPath: params.planningWorkbookPath,
|
||||||
|
referenceWorkbookPath: params.referenceWorkbookPath,
|
||||||
|
...(params.costWorkbookPath !== undefined ? { costWorkbookPath: params.costWorkbookPath } : {}),
|
||||||
|
...(params.notes !== undefined ? { notes: params.notes } : {}),
|
||||||
|
...(params.rosterWorkbookPath !== undefined ? { rosterWorkbookPath: params.rosterWorkbookPath } : {}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async validate_dispo_import_batch(
|
||||||
|
params: {
|
||||||
|
chargeabilityWorkbookPath: string;
|
||||||
|
costWorkbookPath?: string;
|
||||||
|
importBatchId?: string;
|
||||||
|
notes?: string | null;
|
||||||
|
planningWorkbookPath: string;
|
||||||
|
referenceWorkbookPath: string;
|
||||||
|
rosterWorkbookPath?: string;
|
||||||
|
},
|
||||||
|
ctx: ToolContext,
|
||||||
|
) {
|
||||||
|
const caller = deps.createDispoCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.validateImportBatch({
|
||||||
|
chargeabilityWorkbookPath: params.chargeabilityWorkbookPath,
|
||||||
|
planningWorkbookPath: params.planningWorkbookPath,
|
||||||
|
referenceWorkbookPath: params.referenceWorkbookPath,
|
||||||
|
...(params.costWorkbookPath !== undefined ? { costWorkbookPath: params.costWorkbookPath } : {}),
|
||||||
|
...(params.importBatchId !== undefined ? { importBatchId: params.importBatchId } : {}),
|
||||||
|
...(params.notes !== undefined ? { notes: params.notes } : {}),
|
||||||
|
...(params.rosterWorkbookPath !== undefined ? { rosterWorkbookPath: params.rosterWorkbookPath } : {}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async cancel_dispo_import_batch(
|
||||||
|
params: { id: string },
|
||||||
|
ctx: ToolContext,
|
||||||
|
) {
|
||||||
|
const caller = deps.createDispoCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.cancelImportBatch({ id: params.id });
|
||||||
|
},
|
||||||
|
|
||||||
|
async list_dispo_staged_resources(
|
||||||
|
params: { importBatchId: string; status?: StagedRecordStatus; limit?: number; cursor?: string },
|
||||||
|
ctx: ToolContext,
|
||||||
|
) {
|
||||||
|
const limit = clampLimit(params.limit);
|
||||||
|
const caller = deps.createDispoCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.listStagedResources({
|
||||||
|
importBatchId: params.importBatchId,
|
||||||
|
...(params.status !== undefined ? { status: params.status } : {}),
|
||||||
|
...(params.cursor ? { cursor: params.cursor } : {}),
|
||||||
|
...(limit !== undefined ? { limit } : {}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async list_dispo_staged_projects(
|
||||||
|
params: { importBatchId: string; status?: StagedRecordStatus; isTbd?: boolean; limit?: number; cursor?: string },
|
||||||
|
ctx: ToolContext,
|
||||||
|
) {
|
||||||
|
const limit = clampLimit(params.limit);
|
||||||
|
const caller = deps.createDispoCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.listStagedProjects({
|
||||||
|
importBatchId: params.importBatchId,
|
||||||
|
...(params.status !== undefined ? { status: params.status } : {}),
|
||||||
|
...(params.isTbd !== undefined ? { isTbd: params.isTbd } : {}),
|
||||||
|
...(params.cursor ? { cursor: params.cursor } : {}),
|
||||||
|
...(limit !== undefined ? { limit } : {}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async list_dispo_staged_assignments(
|
||||||
|
params: { importBatchId: string; status?: StagedRecordStatus; resourceExternalId?: string; limit?: number; cursor?: string },
|
||||||
|
ctx: ToolContext,
|
||||||
|
) {
|
||||||
|
const limit = clampLimit(params.limit);
|
||||||
|
const caller = deps.createDispoCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.listStagedAssignments({
|
||||||
|
importBatchId: params.importBatchId,
|
||||||
|
...(params.status !== undefined ? { status: params.status } : {}),
|
||||||
|
...(params.resourceExternalId !== undefined ? { resourceExternalId: params.resourceExternalId } : {}),
|
||||||
|
...(params.cursor ? { cursor: params.cursor } : {}),
|
||||||
|
...(limit !== undefined ? { limit } : {}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async list_dispo_staged_vacations(
|
||||||
|
params: { importBatchId: string; resourceExternalId?: string; limit?: number; cursor?: string },
|
||||||
|
ctx: ToolContext,
|
||||||
|
) {
|
||||||
|
const limit = clampLimit(params.limit);
|
||||||
|
const caller = deps.createDispoCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.listStagedVacations({
|
||||||
|
importBatchId: params.importBatchId,
|
||||||
|
...(params.resourceExternalId !== undefined ? { resourceExternalId: params.resourceExternalId } : {}),
|
||||||
|
...(params.cursor ? { cursor: params.cursor } : {}),
|
||||||
|
...(limit !== undefined ? { limit } : {}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async list_dispo_staged_unresolved_records(
|
||||||
|
params: { importBatchId: string; recordType?: DispoStagedRecordType; limit?: number; cursor?: string },
|
||||||
|
ctx: ToolContext,
|
||||||
|
) {
|
||||||
|
const limit = clampLimit(params.limit);
|
||||||
|
const caller = deps.createDispoCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.listStagedUnresolvedRecords({
|
||||||
|
importBatchId: params.importBatchId,
|
||||||
|
...(params.recordType !== undefined ? { recordType: params.recordType } : {}),
|
||||||
|
...(params.cursor ? { cursor: params.cursor } : {}),
|
||||||
|
...(limit !== undefined ? { limit } : {}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async resolve_dispo_staged_record(
|
||||||
|
params: { action: "APPROVE" | "REJECT" | "SKIP"; id: string; recordType: DispoStagedRecordType },
|
||||||
|
ctx: ToolContext,
|
||||||
|
) {
|
||||||
|
const caller = deps.createDispoCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.resolveStagedRecord({
|
||||||
|
action: params.action,
|
||||||
|
id: params.id,
|
||||||
|
recordType: params.recordType,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async commit_dispo_import_batch(
|
||||||
|
params: { allowTbdUnresolved?: boolean; importBatchId: string; importTbdProjects?: boolean },
|
||||||
|
ctx: ToolContext,
|
||||||
|
) {
|
||||||
|
const caller = deps.createDispoCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.commitImportBatch({
|
||||||
|
importBatchId: params.importBatchId,
|
||||||
|
...(params.allowTbdUnresolved !== undefined ? { allowTbdUnresolved: params.allowTbdUnresolved } : {}),
|
||||||
|
...(params.importTbdProjects !== undefined ? { importTbdProjects: params.importTbdProjects } : {}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user