refactor(api): extract dispo router support modules
This commit is contained in:
@@ -0,0 +1,150 @@
|
|||||||
|
import {
|
||||||
|
DispoStagedRecordType,
|
||||||
|
ImportBatchStatus,
|
||||||
|
StagedRecordStatus,
|
||||||
|
} from "@capakraken/db";
|
||||||
|
import { commitDispoImportBatch } from "@capakraken/application";
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import { createAuditEntry } from "../lib/audit.js";
|
||||||
|
|
||||||
|
type AuditDb = Parameters<typeof createAuditEntry>[0]["db"];
|
||||||
|
type CommitDb = Parameters<typeof commitDispoImportBatch>[0];
|
||||||
|
|
||||||
|
export async function cancelImportBatch(
|
||||||
|
db: AuditDb & {
|
||||||
|
importBatch: { findUnique: Function; update: Function };
|
||||||
|
},
|
||||||
|
input: { id: string; userId?: string | undefined },
|
||||||
|
) {
|
||||||
|
const batch = await db.importBatch.findUnique({
|
||||||
|
where: { id: input.id },
|
||||||
|
select: { id: true, status: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!batch) {
|
||||||
|
throw new TRPCError({ code: "NOT_FOUND", message: `Import batch "${input.id}" not found` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const terminalStatuses: ImportBatchStatus[] = [
|
||||||
|
ImportBatchStatus.COMMITTED,
|
||||||
|
ImportBatchStatus.CANCELLED,
|
||||||
|
];
|
||||||
|
if (terminalStatuses.includes(batch.status)) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: `Cannot cancel batch in status "${batch.status}"`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelled = await db.importBatch.update({
|
||||||
|
where: { id: input.id },
|
||||||
|
data: { status: ImportBatchStatus.CANCELLED },
|
||||||
|
});
|
||||||
|
|
||||||
|
void createAuditEntry({
|
||||||
|
db,
|
||||||
|
entityType: "ImportBatch",
|
||||||
|
entityId: input.id,
|
||||||
|
action: "UPDATE",
|
||||||
|
summary: "Cancelled import batch",
|
||||||
|
after: cancelled as Record<string, unknown>,
|
||||||
|
source: "ui",
|
||||||
|
...(input.userId !== undefined ? { userId: input.userId } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function resolveStagedRecord(
|
||||||
|
db: {
|
||||||
|
stagedResource: { update: Function };
|
||||||
|
stagedClient: { update: Function };
|
||||||
|
stagedProject: { update: Function };
|
||||||
|
stagedAssignment: { update: Function };
|
||||||
|
stagedVacation: { update: Function };
|
||||||
|
stagedAvailabilityRule: { update: Function };
|
||||||
|
stagedUnresolvedRecord: { update: Function };
|
||||||
|
},
|
||||||
|
input: { action: "APPROVE" | "REJECT" | "SKIP"; id: string; recordType: DispoStagedRecordType },
|
||||||
|
) {
|
||||||
|
const statusMap: Record<string, StagedRecordStatus> = {
|
||||||
|
APPROVE: StagedRecordStatus.APPROVED,
|
||||||
|
REJECT: StagedRecordStatus.REJECTED,
|
||||||
|
SKIP: StagedRecordStatus.REJECTED,
|
||||||
|
};
|
||||||
|
const nextStatus = statusMap[input.action]!;
|
||||||
|
|
||||||
|
switch (input.recordType) {
|
||||||
|
case DispoStagedRecordType.RESOURCE:
|
||||||
|
return db.stagedResource.update({
|
||||||
|
where: { id: input.id },
|
||||||
|
data: { status: nextStatus },
|
||||||
|
});
|
||||||
|
case DispoStagedRecordType.CLIENT:
|
||||||
|
return db.stagedClient.update({
|
||||||
|
where: { id: input.id },
|
||||||
|
data: { status: nextStatus },
|
||||||
|
});
|
||||||
|
case DispoStagedRecordType.PROJECT:
|
||||||
|
return db.stagedProject.update({
|
||||||
|
where: { id: input.id },
|
||||||
|
data: { status: nextStatus },
|
||||||
|
});
|
||||||
|
case DispoStagedRecordType.ASSIGNMENT:
|
||||||
|
return db.stagedAssignment.update({
|
||||||
|
where: { id: input.id },
|
||||||
|
data: { status: nextStatus },
|
||||||
|
});
|
||||||
|
case DispoStagedRecordType.VACATION:
|
||||||
|
return db.stagedVacation.update({
|
||||||
|
where: { id: input.id },
|
||||||
|
data: { status: nextStatus },
|
||||||
|
});
|
||||||
|
case DispoStagedRecordType.AVAILABILITY_RULE:
|
||||||
|
return db.stagedAvailabilityRule.update({
|
||||||
|
where: { id: input.id },
|
||||||
|
data: { status: nextStatus },
|
||||||
|
});
|
||||||
|
case DispoStagedRecordType.UNRESOLVED:
|
||||||
|
return db.stagedUnresolvedRecord.update({
|
||||||
|
where: { id: input.id },
|
||||||
|
data: { status: nextStatus },
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: `Unknown record type: ${input.recordType as string}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function commitImportBatch(
|
||||||
|
db: CommitDb,
|
||||||
|
input: {
|
||||||
|
importBatchId: string;
|
||||||
|
allowTbdUnresolved?: boolean | undefined;
|
||||||
|
importTbdProjects?: boolean | undefined;
|
||||||
|
userId?: string | undefined;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const result = await commitDispoImportBatch(db, {
|
||||||
|
importBatchId: input.importBatchId,
|
||||||
|
...(input.allowTbdUnresolved !== undefined ? { allowTbdUnresolved: input.allowTbdUnresolved } : {}),
|
||||||
|
...(input.importTbdProjects !== undefined ? { importTbdProjects: input.importTbdProjects } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const counts = result as unknown as Record<string, unknown>;
|
||||||
|
void createAuditEntry({
|
||||||
|
db: db as AuditDb,
|
||||||
|
entityType: "ImportBatch",
|
||||||
|
entityId: input.importBatchId,
|
||||||
|
entityName: input.importBatchId,
|
||||||
|
action: "IMPORT",
|
||||||
|
summary: `Committed import batch (${JSON.stringify(counts)})`,
|
||||||
|
after: counts,
|
||||||
|
source: "ui",
|
||||||
|
...(input.userId !== undefined ? { userId: input.userId } : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
import {
|
||||||
|
ImportBatchStatus,
|
||||||
|
StagedRecordStatus,
|
||||||
|
} from "@capakraken/db";
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
|
||||||
|
type PaginationInput = {
|
||||||
|
cursor?: string | undefined;
|
||||||
|
limit: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function toPaginatedResult<T extends { id: string }>(items: T[], limit: number) {
|
||||||
|
let nextCursor: string | undefined;
|
||||||
|
if (items.length > limit) {
|
||||||
|
const next = items.pop();
|
||||||
|
nextCursor = next?.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { items, nextCursor };
|
||||||
|
}
|
||||||
|
|
||||||
|
function toCursorPagination(input: PaginationInput) {
|
||||||
|
return {
|
||||||
|
take: input.limit + 1,
|
||||||
|
...(input.cursor ? { cursor: { id: input.cursor }, skip: 1 } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listImportBatches(
|
||||||
|
db: { importBatch: { findMany: Function } },
|
||||||
|
input: PaginationInput & { status?: ImportBatchStatus | undefined },
|
||||||
|
) {
|
||||||
|
const items = await db.importBatch.findMany({
|
||||||
|
where: {
|
||||||
|
...(input.status !== undefined ? { status: input.status } : {}),
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: "desc" },
|
||||||
|
...toCursorPagination(input),
|
||||||
|
});
|
||||||
|
|
||||||
|
return toPaginatedResult(items, input.limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getImportBatch(
|
||||||
|
db: {
|
||||||
|
importBatch: { findUnique: Function };
|
||||||
|
stagedResource: { count: Function };
|
||||||
|
stagedClient: { count: Function };
|
||||||
|
stagedProject: { count: Function };
|
||||||
|
stagedAssignment: { count: Function };
|
||||||
|
stagedVacation: { count: Function };
|
||||||
|
stagedAvailabilityRule: { count: Function };
|
||||||
|
stagedUnresolvedRecord: { count: Function };
|
||||||
|
},
|
||||||
|
id: string,
|
||||||
|
) {
|
||||||
|
const batch = await db.importBatch.findUnique({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!batch) {
|
||||||
|
throw new TRPCError({ code: "NOT_FOUND", message: `Import batch "${id}" not found` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const [
|
||||||
|
resourceCount,
|
||||||
|
clientCount,
|
||||||
|
projectCount,
|
||||||
|
assignmentCount,
|
||||||
|
vacationCount,
|
||||||
|
availabilityRuleCount,
|
||||||
|
unresolvedCount,
|
||||||
|
] = await Promise.all([
|
||||||
|
db.stagedResource.count({ where: { importBatchId: id } }),
|
||||||
|
db.stagedClient.count({ where: { importBatchId: id } }),
|
||||||
|
db.stagedProject.count({ where: { importBatchId: id } }),
|
||||||
|
db.stagedAssignment.count({ where: { importBatchId: id } }),
|
||||||
|
db.stagedVacation.count({ where: { importBatchId: id } }),
|
||||||
|
db.stagedAvailabilityRule.count({ where: { importBatchId: id } }),
|
||||||
|
db.stagedUnresolvedRecord.count({ where: { importBatchId: id } }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...batch,
|
||||||
|
counts: {
|
||||||
|
assignmentCount,
|
||||||
|
availabilityRuleCount,
|
||||||
|
clientCount,
|
||||||
|
projectCount,
|
||||||
|
resourceCount,
|
||||||
|
unresolvedCount,
|
||||||
|
vacationCount,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listStagedResources(
|
||||||
|
db: { stagedResource: { findMany: Function } },
|
||||||
|
input: PaginationInput & {
|
||||||
|
importBatchId: string;
|
||||||
|
status?: StagedRecordStatus | undefined;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const items = await db.stagedResource.findMany({
|
||||||
|
where: {
|
||||||
|
importBatchId: input.importBatchId,
|
||||||
|
...(input.status !== undefined ? { status: input.status } : {}),
|
||||||
|
},
|
||||||
|
orderBy: { canonicalExternalId: "asc" },
|
||||||
|
...toCursorPagination(input),
|
||||||
|
});
|
||||||
|
|
||||||
|
return toPaginatedResult(items, input.limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listStagedProjects(
|
||||||
|
db: { stagedProject: { findMany: Function } },
|
||||||
|
input: PaginationInput & {
|
||||||
|
importBatchId: string;
|
||||||
|
isTbd?: boolean | undefined;
|
||||||
|
status?: StagedRecordStatus | undefined;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const items = await db.stagedProject.findMany({
|
||||||
|
where: {
|
||||||
|
importBatchId: input.importBatchId,
|
||||||
|
...(input.status !== undefined ? { status: input.status } : {}),
|
||||||
|
...(input.isTbd !== undefined ? { isTbd: input.isTbd } : {}),
|
||||||
|
},
|
||||||
|
orderBy: { projectKey: "asc" },
|
||||||
|
...toCursorPagination(input),
|
||||||
|
});
|
||||||
|
|
||||||
|
return toPaginatedResult(items, input.limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listStagedAssignments(
|
||||||
|
db: { stagedAssignment: { findMany: Function } },
|
||||||
|
input: PaginationInput & {
|
||||||
|
importBatchId: string;
|
||||||
|
resourceExternalId?: string | undefined;
|
||||||
|
status?: StagedRecordStatus | undefined;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const items = await db.stagedAssignment.findMany({
|
||||||
|
where: {
|
||||||
|
importBatchId: input.importBatchId,
|
||||||
|
...(input.status !== undefined ? { status: input.status } : {}),
|
||||||
|
...(input.resourceExternalId !== undefined ? { resourceExternalId: input.resourceExternalId } : {}),
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: "asc" },
|
||||||
|
...toCursorPagination(input),
|
||||||
|
});
|
||||||
|
|
||||||
|
return toPaginatedResult(items, input.limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listStagedVacations(
|
||||||
|
db: { stagedVacation: { findMany: Function } },
|
||||||
|
input: PaginationInput & {
|
||||||
|
importBatchId: string;
|
||||||
|
resourceExternalId?: string | undefined;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const items = await db.stagedVacation.findMany({
|
||||||
|
where: {
|
||||||
|
importBatchId: input.importBatchId,
|
||||||
|
...(input.resourceExternalId !== undefined ? { resourceExternalId: input.resourceExternalId } : {}),
|
||||||
|
},
|
||||||
|
orderBy: { startDate: "asc" },
|
||||||
|
...toCursorPagination(input),
|
||||||
|
});
|
||||||
|
|
||||||
|
return toPaginatedResult(items, input.limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listStagedUnresolvedRecords(
|
||||||
|
db: { stagedUnresolvedRecord: { findMany: Function } },
|
||||||
|
input: PaginationInput & {
|
||||||
|
importBatchId: string;
|
||||||
|
recordType?: string | undefined;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const items = await db.stagedUnresolvedRecord.findMany({
|
||||||
|
where: {
|
||||||
|
importBatchId: input.importBatchId,
|
||||||
|
...(input.recordType !== undefined ? { recordType: input.recordType } : {}),
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: "asc" },
|
||||||
|
...toCursorPagination(input),
|
||||||
|
});
|
||||||
|
|
||||||
|
return toPaginatedResult(items, input.limit);
|
||||||
|
}
|
||||||
@@ -5,13 +5,20 @@ import {
|
|||||||
} from "@capakraken/db";
|
} from "@capakraken/db";
|
||||||
import {
|
import {
|
||||||
assessDispoImportReadiness,
|
assessDispoImportReadiness,
|
||||||
commitDispoImportBatch,
|
|
||||||
stageDispoImportBatch,
|
stageDispoImportBatch,
|
||||||
} from "@capakraken/application";
|
} from "@capakraken/application";
|
||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { adminProcedure, createTRPCRouter } from "../trpc.js";
|
import { adminProcedure, createTRPCRouter } from "../trpc.js";
|
||||||
import { createAuditEntry } from "../lib/audit.js";
|
import { commitImportBatch, cancelImportBatch, resolveStagedRecord } from "./dispo-management.js";
|
||||||
|
import {
|
||||||
|
getImportBatch,
|
||||||
|
listImportBatches,
|
||||||
|
listStagedAssignments,
|
||||||
|
listStagedProjects,
|
||||||
|
listStagedResources,
|
||||||
|
listStagedUnresolvedRecords,
|
||||||
|
listStagedVacations,
|
||||||
|
} from "./dispo-read.js";
|
||||||
|
|
||||||
// ─── Shared schemas ──────────────────────────────────────────────────────────
|
// ─── Shared schemas ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -93,24 +100,7 @@ export const dispoRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const { cursor, limit, status } = input;
|
return listImportBatches(ctx.db, input);
|
||||||
|
|
||||||
const items = await ctx.db.importBatch.findMany({
|
|
||||||
where: {
|
|
||||||
...(status !== undefined ? { status } : {}),
|
|
||||||
},
|
|
||||||
orderBy: { createdAt: "desc" },
|
|
||||||
take: limit + 1,
|
|
||||||
...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
|
|
||||||
});
|
|
||||||
|
|
||||||
let nextCursor: string | undefined;
|
|
||||||
if (items.length > limit) {
|
|
||||||
const next = items.pop();
|
|
||||||
nextCursor = next?.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { items, nextCursor };
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// ── 4. getImportBatch ────────────────────────────────────────────────────
|
// ── 4. getImportBatch ────────────────────────────────────────────────────
|
||||||
@@ -118,44 +108,7 @@ export const dispoRouter = createTRPCRouter({
|
|||||||
getImportBatch: adminProcedure
|
getImportBatch: adminProcedure
|
||||||
.input(z.object({ id: z.string() }))
|
.input(z.object({ id: z.string() }))
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const batch = await ctx.db.importBatch.findUnique({
|
return getImportBatch(ctx.db, input.id);
|
||||||
where: { id: input.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!batch) {
|
|
||||||
throw new TRPCError({ code: "NOT_FOUND", message: `Import batch "${input.id}" not found` });
|
|
||||||
}
|
|
||||||
|
|
||||||
const [
|
|
||||||
resourceCount,
|
|
||||||
clientCount,
|
|
||||||
projectCount,
|
|
||||||
assignmentCount,
|
|
||||||
vacationCount,
|
|
||||||
availabilityRuleCount,
|
|
||||||
unresolvedCount,
|
|
||||||
] = await Promise.all([
|
|
||||||
ctx.db.stagedResource.count({ where: { importBatchId: input.id } }),
|
|
||||||
ctx.db.stagedClient.count({ where: { importBatchId: input.id } }),
|
|
||||||
ctx.db.stagedProject.count({ where: { importBatchId: input.id } }),
|
|
||||||
ctx.db.stagedAssignment.count({ where: { importBatchId: input.id } }),
|
|
||||||
ctx.db.stagedVacation.count({ where: { importBatchId: input.id } }),
|
|
||||||
ctx.db.stagedAvailabilityRule.count({ where: { importBatchId: input.id } }),
|
|
||||||
ctx.db.stagedUnresolvedRecord.count({ where: { importBatchId: input.id } }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...batch,
|
|
||||||
counts: {
|
|
||||||
assignmentCount,
|
|
||||||
availabilityRuleCount,
|
|
||||||
clientCount,
|
|
||||||
projectCount,
|
|
||||||
resourceCount,
|
|
||||||
unresolvedCount,
|
|
||||||
vacationCount,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// ── 5. cancelImportBatch ─────────────────────────────────────────────────
|
// ── 5. cancelImportBatch ─────────────────────────────────────────────────
|
||||||
@@ -163,43 +116,7 @@ export const dispoRouter = createTRPCRouter({
|
|||||||
cancelImportBatch: adminProcedure
|
cancelImportBatch: adminProcedure
|
||||||
.input(z.object({ id: z.string() }))
|
.input(z.object({ id: z.string() }))
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const batch = await ctx.db.importBatch.findUnique({
|
return cancelImportBatch(ctx.db, { id: input.id, userId: ctx.dbUser?.id });
|
||||||
where: { id: input.id },
|
|
||||||
select: { id: true, status: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!batch) {
|
|
||||||
throw new TRPCError({ code: "NOT_FOUND", message: `Import batch "${input.id}" not found` });
|
|
||||||
}
|
|
||||||
|
|
||||||
const terminalStatuses: ImportBatchStatus[] = [
|
|
||||||
ImportBatchStatus.COMMITTED,
|
|
||||||
ImportBatchStatus.CANCELLED,
|
|
||||||
];
|
|
||||||
if (terminalStatuses.includes(batch.status)) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: `Cannot cancel batch in status "${batch.status}"`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelled = await ctx.db.importBatch.update({
|
|
||||||
where: { id: input.id },
|
|
||||||
data: { status: ImportBatchStatus.CANCELLED },
|
|
||||||
});
|
|
||||||
|
|
||||||
void createAuditEntry({
|
|
||||||
db: ctx.db,
|
|
||||||
entityType: "ImportBatch",
|
|
||||||
entityId: input.id,
|
|
||||||
action: "UPDATE",
|
|
||||||
userId: ctx.dbUser?.id,
|
|
||||||
summary: "Cancelled import batch",
|
|
||||||
after: cancelled as unknown as Record<string, unknown>,
|
|
||||||
source: "ui",
|
|
||||||
});
|
|
||||||
|
|
||||||
return cancelled;
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// ── 6. listStagedResources ───────────────────────────────────────────────
|
// ── 6. listStagedResources ───────────────────────────────────────────────
|
||||||
@@ -212,25 +129,7 @@ export const dispoRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const { cursor, importBatchId, limit, status } = input;
|
return listStagedResources(ctx.db, input);
|
||||||
|
|
||||||
const items = await ctx.db.stagedResource.findMany({
|
|
||||||
where: {
|
|
||||||
importBatchId,
|
|
||||||
...(status !== undefined ? { status } : {}),
|
|
||||||
},
|
|
||||||
orderBy: { canonicalExternalId: "asc" },
|
|
||||||
take: limit + 1,
|
|
||||||
...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
|
|
||||||
});
|
|
||||||
|
|
||||||
let nextCursor: string | undefined;
|
|
||||||
if (items.length > limit) {
|
|
||||||
const next = items.pop();
|
|
||||||
nextCursor = next?.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { items, nextCursor };
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// ── 7. listStagedProjects ────────────────────────────────────────────────
|
// ── 7. listStagedProjects ────────────────────────────────────────────────
|
||||||
@@ -244,26 +143,7 @@ export const dispoRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const { cursor, importBatchId, isTbd, limit, status } = input;
|
return listStagedProjects(ctx.db, input);
|
||||||
|
|
||||||
const items = await ctx.db.stagedProject.findMany({
|
|
||||||
where: {
|
|
||||||
importBatchId,
|
|
||||||
...(status !== undefined ? { status } : {}),
|
|
||||||
...(isTbd !== undefined ? { isTbd } : {}),
|
|
||||||
},
|
|
||||||
orderBy: { projectKey: "asc" },
|
|
||||||
take: limit + 1,
|
|
||||||
...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
|
|
||||||
});
|
|
||||||
|
|
||||||
let nextCursor: string | undefined;
|
|
||||||
if (items.length > limit) {
|
|
||||||
const next = items.pop();
|
|
||||||
nextCursor = next?.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { items, nextCursor };
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// ── 8. listStagedAssignments ─────────────────────────────────────────────
|
// ── 8. listStagedAssignments ─────────────────────────────────────────────
|
||||||
@@ -277,26 +157,7 @@ export const dispoRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const { cursor, importBatchId, limit, resourceExternalId, status } = input;
|
return listStagedAssignments(ctx.db, input);
|
||||||
|
|
||||||
const items = await ctx.db.stagedAssignment.findMany({
|
|
||||||
where: {
|
|
||||||
importBatchId,
|
|
||||||
...(status !== undefined ? { status } : {}),
|
|
||||||
...(resourceExternalId !== undefined ? { resourceExternalId } : {}),
|
|
||||||
},
|
|
||||||
orderBy: { createdAt: "asc" },
|
|
||||||
take: limit + 1,
|
|
||||||
...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
|
|
||||||
});
|
|
||||||
|
|
||||||
let nextCursor: string | undefined;
|
|
||||||
if (items.length > limit) {
|
|
||||||
const next = items.pop();
|
|
||||||
nextCursor = next?.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { items, nextCursor };
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// ── 9. listStagedVacations ───────────────────────────────────────────────
|
// ── 9. listStagedVacations ───────────────────────────────────────────────
|
||||||
@@ -309,25 +170,7 @@ export const dispoRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const { cursor, importBatchId, limit, resourceExternalId } = input;
|
return listStagedVacations(ctx.db, input);
|
||||||
|
|
||||||
const items = await ctx.db.stagedVacation.findMany({
|
|
||||||
where: {
|
|
||||||
importBatchId,
|
|
||||||
...(resourceExternalId !== undefined ? { resourceExternalId } : {}),
|
|
||||||
},
|
|
||||||
orderBy: { startDate: "asc" },
|
|
||||||
take: limit + 1,
|
|
||||||
...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
|
|
||||||
});
|
|
||||||
|
|
||||||
let nextCursor: string | undefined;
|
|
||||||
if (items.length > limit) {
|
|
||||||
const next = items.pop();
|
|
||||||
nextCursor = next?.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { items, nextCursor };
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// ── 10. listStagedUnresolvedRecords ──────────────────────────────────────
|
// ── 10. listStagedUnresolvedRecords ──────────────────────────────────────
|
||||||
@@ -340,25 +183,7 @@ export const dispoRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.query(async ({ ctx, input }) => {
|
.query(async ({ ctx, input }) => {
|
||||||
const { cursor, importBatchId, limit, recordType } = input;
|
return listStagedUnresolvedRecords(ctx.db, input);
|
||||||
|
|
||||||
const items = await ctx.db.stagedUnresolvedRecord.findMany({
|
|
||||||
where: {
|
|
||||||
importBatchId,
|
|
||||||
...(recordType !== undefined ? { recordType } : {}),
|
|
||||||
},
|
|
||||||
orderBy: { createdAt: "asc" },
|
|
||||||
take: limit + 1,
|
|
||||||
...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
|
|
||||||
});
|
|
||||||
|
|
||||||
let nextCursor: string | undefined;
|
|
||||||
if (items.length > limit) {
|
|
||||||
const next = items.pop();
|
|
||||||
nextCursor = next?.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { items, nextCursor };
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// ── 11. resolveStagedRecord ──────────────────────────────────────────────
|
// ── 11. resolveStagedRecord ──────────────────────────────────────────────
|
||||||
@@ -372,56 +197,7 @@ export const dispoRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const statusMap: Record<string, StagedRecordStatus> = {
|
return resolveStagedRecord(ctx.db, input);
|
||||||
APPROVE: StagedRecordStatus.APPROVED,
|
|
||||||
REJECT: StagedRecordStatus.REJECTED,
|
|
||||||
SKIP: StagedRecordStatus.REJECTED,
|
|
||||||
};
|
|
||||||
const nextStatus = statusMap[input.action]!;
|
|
||||||
|
|
||||||
// Delegate table lookup based on record type
|
|
||||||
switch (input.recordType) {
|
|
||||||
case DispoStagedRecordType.RESOURCE:
|
|
||||||
return ctx.db.stagedResource.update({
|
|
||||||
where: { id: input.id },
|
|
||||||
data: { status: nextStatus },
|
|
||||||
});
|
|
||||||
case DispoStagedRecordType.CLIENT:
|
|
||||||
return ctx.db.stagedClient.update({
|
|
||||||
where: { id: input.id },
|
|
||||||
data: { status: nextStatus },
|
|
||||||
});
|
|
||||||
case DispoStagedRecordType.PROJECT:
|
|
||||||
return ctx.db.stagedProject.update({
|
|
||||||
where: { id: input.id },
|
|
||||||
data: { status: nextStatus },
|
|
||||||
});
|
|
||||||
case DispoStagedRecordType.ASSIGNMENT:
|
|
||||||
return ctx.db.stagedAssignment.update({
|
|
||||||
where: { id: input.id },
|
|
||||||
data: { status: nextStatus },
|
|
||||||
});
|
|
||||||
case DispoStagedRecordType.VACATION:
|
|
||||||
return ctx.db.stagedVacation.update({
|
|
||||||
where: { id: input.id },
|
|
||||||
data: { status: nextStatus },
|
|
||||||
});
|
|
||||||
case DispoStagedRecordType.AVAILABILITY_RULE:
|
|
||||||
return ctx.db.stagedAvailabilityRule.update({
|
|
||||||
where: { id: input.id },
|
|
||||||
data: { status: nextStatus },
|
|
||||||
});
|
|
||||||
case DispoStagedRecordType.UNRESOLVED:
|
|
||||||
return ctx.db.stagedUnresolvedRecord.update({
|
|
||||||
where: { id: input.id },
|
|
||||||
data: { status: nextStatus },
|
|
||||||
});
|
|
||||||
default:
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: `Unknown record type: ${input.recordType as string}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// ── 12. commitImportBatch ────────────────────────────────────────────────
|
// ── 12. commitImportBatch ────────────────────────────────────────────────
|
||||||
@@ -435,25 +211,11 @@ export const dispoRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const result = await commitDispoImportBatch(ctx.db, {
|
return commitImportBatch(ctx.db, {
|
||||||
importBatchId: input.importBatchId,
|
importBatchId: input.importBatchId,
|
||||||
...(input.allowTbdUnresolved !== undefined ? { allowTbdUnresolved: input.allowTbdUnresolved } : {}),
|
allowTbdUnresolved: input.allowTbdUnresolved,
|
||||||
...(input.importTbdProjects !== undefined ? { importTbdProjects: input.importTbdProjects } : {}),
|
importTbdProjects: input.importTbdProjects,
|
||||||
});
|
|
||||||
|
|
||||||
const counts = result as unknown as Record<string, unknown>;
|
|
||||||
void createAuditEntry({
|
|
||||||
db: ctx.db,
|
|
||||||
entityType: "ImportBatch",
|
|
||||||
entityId: input.importBatchId,
|
|
||||||
entityName: input.importBatchId,
|
|
||||||
action: "IMPORT",
|
|
||||||
userId: ctx.dbUser?.id,
|
userId: ctx.dbUser?.id,
|
||||||
summary: `Committed import batch (${JSON.stringify(counts)})`,
|
|
||||||
after: counts,
|
|
||||||
source: "ui",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user