b41c1d2501
CI / Architecture Guardrails (push) Successful in 2m38s
CI / Assistant Split Regression (push) Successful in 3m33s
CI / Typecheck (push) Successful in 3m51s
CI / Lint (push) Successful in 5m2s
CI / E2E Tests (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / Release Images (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61) Co-authored-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com> Co-committed-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
221 lines
8.1 KiB
TypeScript
221 lines
8.1 KiB
TypeScript
import path from "node:path";
|
|
import { DispoStagedRecordType, ImportBatchStatus, StagedRecordStatus } from "@nexus/db";
|
|
import {
|
|
assessDispoImportReadiness,
|
|
stageDispoImportBatch as stageDispoImportBatchApplication,
|
|
} from "@nexus/application";
|
|
import { z } from "zod";
|
|
import type { TRPCContext } from "../trpc.js";
|
|
import {
|
|
cancelImportBatch as cancelImportBatchMutation,
|
|
commitImportBatch as commitImportBatchMutation,
|
|
resolveStagedRecord as resolveStagedRecordMutation,
|
|
} from "./dispo-management.js";
|
|
import {
|
|
getImportBatch as getImportBatchQuery,
|
|
listImportBatches as listImportBatchesQuery,
|
|
listStagedAssignments as listStagedAssignmentsQuery,
|
|
listStagedProjects as listStagedProjectsQuery,
|
|
listStagedResources as listStagedResourcesQuery,
|
|
listStagedUnresolvedRecords as listStagedUnresolvedRecordsQuery,
|
|
listStagedVacations as listStagedVacationsQuery,
|
|
} from "./dispo-read.js";
|
|
|
|
type DispoProcedureContext = Pick<TRPCContext, "db" | "dbUser">;
|
|
|
|
const paginationSchema = z.object({
|
|
cursor: z.string().optional(),
|
|
limit: z.number().int().min(1).max(200).default(50),
|
|
});
|
|
|
|
const importBatchStatusSchema = z.nativeEnum(ImportBatchStatus);
|
|
const stagedRecordStatusSchema = z.nativeEnum(StagedRecordStatus);
|
|
const stagedRecordTypeSchema = z.nativeEnum(DispoStagedRecordType);
|
|
// Reject absolute paths and paths that contain `..` segments at the router
|
|
// boundary. The workbook reader re-validates against DISPO_IMPORT_DIR as
|
|
// defence-in-depth, but rejecting early here gives a clearer error to admin
|
|
// users and shrinks the attack surface if the reader is ever called with a
|
|
// different allowlist policy.
|
|
const workbookPathSchema = z
|
|
.string()
|
|
.trim()
|
|
.min(1, "Workbook path is required.")
|
|
.max(4096, "Workbook path is too long.")
|
|
.refine((value) => value.toLowerCase().endsWith(".xlsx"), {
|
|
message: "Only .xlsx workbook paths are supported.",
|
|
})
|
|
.refine((value) => !path.isAbsolute(value), {
|
|
message: "Workbook path must be relative to the configured import directory.",
|
|
})
|
|
.refine((value) => !value.split(/[\\/]/).some((segment) => segment === ".."), {
|
|
message: "Workbook path must not contain parent-directory segments.",
|
|
});
|
|
|
|
export const stageImportBatchInputSchema = z.object({
|
|
chargeabilityWorkbookPath: workbookPathSchema,
|
|
costWorkbookPath: workbookPathSchema.optional(),
|
|
notes: z.string().nullish(),
|
|
planningWorkbookPath: workbookPathSchema,
|
|
referenceWorkbookPath: workbookPathSchema,
|
|
rosterWorkbookPath: workbookPathSchema.optional(),
|
|
});
|
|
|
|
export const validateImportBatchInputSchema = z.object({
|
|
chargeabilityWorkbookPath: workbookPathSchema,
|
|
costWorkbookPath: workbookPathSchema.optional(),
|
|
importBatchId: z.string().optional(),
|
|
notes: z.string().nullish(),
|
|
planningWorkbookPath: workbookPathSchema,
|
|
referenceWorkbookPath: workbookPathSchema,
|
|
rosterWorkbookPath: workbookPathSchema.optional(),
|
|
});
|
|
|
|
export const listImportBatchesInputSchema = paginationSchema.extend({
|
|
status: importBatchStatusSchema.optional(),
|
|
});
|
|
|
|
export const importBatchIdInputSchema = z.object({
|
|
id: z.string(),
|
|
});
|
|
|
|
export const listStagedResourcesInputSchema = paginationSchema.extend({
|
|
importBatchId: z.string(),
|
|
status: stagedRecordStatusSchema.optional(),
|
|
});
|
|
|
|
export const listStagedProjectsInputSchema = paginationSchema.extend({
|
|
importBatchId: z.string(),
|
|
isTbd: z.boolean().optional(),
|
|
status: stagedRecordStatusSchema.optional(),
|
|
});
|
|
|
|
export const listStagedAssignmentsInputSchema = paginationSchema.extend({
|
|
importBatchId: z.string(),
|
|
resourceExternalId: z.string().optional(),
|
|
status: stagedRecordStatusSchema.optional(),
|
|
});
|
|
|
|
export const listStagedVacationsInputSchema = paginationSchema.extend({
|
|
importBatchId: z.string(),
|
|
resourceExternalId: z.string().optional(),
|
|
});
|
|
|
|
export const listStagedUnresolvedRecordsInputSchema = paginationSchema.extend({
|
|
importBatchId: z.string(),
|
|
recordType: stagedRecordTypeSchema.optional(),
|
|
});
|
|
|
|
export const resolveStagedRecordInputSchema = z.object({
|
|
action: z.enum(["APPROVE", "REJECT", "SKIP"]),
|
|
id: z.string(),
|
|
recordType: stagedRecordTypeSchema,
|
|
});
|
|
|
|
export const commitImportBatchInputSchema = z.object({
|
|
allowTbdUnresolved: z.boolean().optional(),
|
|
importBatchId: z.string(),
|
|
importTbdProjects: z.boolean().optional(),
|
|
});
|
|
|
|
type StageImportBatchInput = z.infer<typeof stageImportBatchInputSchema>;
|
|
type ValidateImportBatchInput = z.infer<typeof validateImportBatchInputSchema>;
|
|
type ListImportBatchesInput = z.infer<typeof listImportBatchesInputSchema>;
|
|
type ImportBatchIdInput = z.infer<typeof importBatchIdInputSchema>;
|
|
type ListStagedResourcesInput = z.infer<typeof listStagedResourcesInputSchema>;
|
|
type ListStagedProjectsInput = z.infer<typeof listStagedProjectsInputSchema>;
|
|
type ListStagedAssignmentsInput = z.infer<typeof listStagedAssignmentsInputSchema>;
|
|
type ListStagedVacationsInput = z.infer<typeof listStagedVacationsInputSchema>;
|
|
type ListStagedUnresolvedRecordsInput = z.infer<typeof listStagedUnresolvedRecordsInputSchema>;
|
|
type ResolveStagedRecordInput = z.infer<typeof resolveStagedRecordInputSchema>;
|
|
type CommitImportBatchInput = z.infer<typeof commitImportBatchInputSchema>;
|
|
|
|
export async function stageImportBatch(ctx: DispoProcedureContext, input: StageImportBatchInput) {
|
|
return stageDispoImportBatchApplication(ctx.db, {
|
|
chargeabilityWorkbookPath: input.chargeabilityWorkbookPath,
|
|
planningWorkbookPath: input.planningWorkbookPath,
|
|
referenceWorkbookPath: input.referenceWorkbookPath,
|
|
...(input.costWorkbookPath !== undefined ? { costWorkbookPath: input.costWorkbookPath } : {}),
|
|
...(input.notes !== undefined ? { notes: input.notes } : {}),
|
|
...(input.rosterWorkbookPath !== undefined
|
|
? { rosterWorkbookPath: input.rosterWorkbookPath }
|
|
: {}),
|
|
});
|
|
}
|
|
|
|
export async function validateImportBatch(input: ValidateImportBatchInput) {
|
|
return assessDispoImportReadiness({
|
|
chargeabilityWorkbookPath: input.chargeabilityWorkbookPath,
|
|
planningWorkbookPath: input.planningWorkbookPath,
|
|
referenceWorkbookPath: input.referenceWorkbookPath,
|
|
...(input.costWorkbookPath !== undefined ? { costWorkbookPath: input.costWorkbookPath } : {}),
|
|
...(input.importBatchId !== undefined ? { importBatchId: input.importBatchId } : {}),
|
|
...(input.notes !== undefined ? { notes: input.notes } : {}),
|
|
...(input.rosterWorkbookPath !== undefined
|
|
? { rosterWorkbookPath: input.rosterWorkbookPath }
|
|
: {}),
|
|
});
|
|
}
|
|
|
|
export async function listImportBatches(ctx: DispoProcedureContext, input: ListImportBatchesInput) {
|
|
return listImportBatchesQuery(ctx.db, input);
|
|
}
|
|
|
|
export async function getImportBatch(ctx: DispoProcedureContext, input: ImportBatchIdInput) {
|
|
return getImportBatchQuery(ctx.db, input.id);
|
|
}
|
|
|
|
export async function cancelImportBatch(ctx: DispoProcedureContext, input: ImportBatchIdInput) {
|
|
return cancelImportBatchMutation(ctx.db, { id: input.id, userId: ctx.dbUser?.id });
|
|
}
|
|
|
|
export async function listStagedResources(
|
|
ctx: DispoProcedureContext,
|
|
input: ListStagedResourcesInput,
|
|
) {
|
|
return listStagedResourcesQuery(ctx.db, input);
|
|
}
|
|
|
|
export async function listStagedProjects(
|
|
ctx: DispoProcedureContext,
|
|
input: ListStagedProjectsInput,
|
|
) {
|
|
return listStagedProjectsQuery(ctx.db, input);
|
|
}
|
|
|
|
export async function listStagedAssignments(
|
|
ctx: DispoProcedureContext,
|
|
input: ListStagedAssignmentsInput,
|
|
) {
|
|
return listStagedAssignmentsQuery(ctx.db, input);
|
|
}
|
|
|
|
export async function listStagedVacations(
|
|
ctx: DispoProcedureContext,
|
|
input: ListStagedVacationsInput,
|
|
) {
|
|
return listStagedVacationsQuery(ctx.db, input);
|
|
}
|
|
|
|
export async function listStagedUnresolvedRecords(
|
|
ctx: DispoProcedureContext,
|
|
input: ListStagedUnresolvedRecordsInput,
|
|
) {
|
|
return listStagedUnresolvedRecordsQuery(ctx.db, input);
|
|
}
|
|
|
|
export async function resolveStagedRecord(
|
|
ctx: DispoProcedureContext,
|
|
input: ResolveStagedRecordInput,
|
|
) {
|
|
return resolveStagedRecordMutation(ctx.db, input);
|
|
}
|
|
|
|
export async function commitImportBatch(ctx: DispoProcedureContext, input: CommitImportBatchInput) {
|
|
return commitImportBatchMutation(ctx.db, {
|
|
importBatchId: input.importBatchId,
|
|
allowTbdUnresolved: input.allowTbdUnresolved,
|
|
importTbdProjects: input.importTbdProjects,
|
|
userId: ctx.dbUser?.id,
|
|
});
|
|
}
|