feat: add inspection object resource workflow
This commit is contained in:
@@ -91,6 +91,7 @@ async function validateRegistrationRunInput(input: {
|
||||
if (!cad) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "选中的 CAD 文件不存在。" });
|
||||
}
|
||||
await validateTaskInputCompatibility(input.taskId, pointcloud, cad);
|
||||
|
||||
const registeredChildren = pointcloudFiles
|
||||
.filter((row) => row.source === "registered" && row.sourcePointcloudFileId === pointcloud.id)
|
||||
@@ -104,6 +105,29 @@ async function validateRegistrationRunInput(input: {
|
||||
}
|
||||
}
|
||||
|
||||
async function validateTaskInputCompatibility(
|
||||
taskId: number,
|
||||
pointcloud: Awaited<ReturnType<typeof db.listTaskPointcloudFiles>>[number],
|
||||
cad: Awaited<ReturnType<typeof db.listTaskCadFiles>>[number],
|
||||
) {
|
||||
const task = await db.getInspectionTaskById(taskId);
|
||||
if (!task) throw new TRPCError({ code: "NOT_FOUND", message: "检测任务不存在。" });
|
||||
if (!task.inspectionObjectId) {
|
||||
throw new TRPCError({ code: "PRECONDITION_FAILED", message: "历史任务尚未确认检测对象,请联系管理员完成归类后再运行算法。" });
|
||||
}
|
||||
if (pointcloud.source === "model_library" && pointcloud.sourceModelPointcloudId) {
|
||||
const source = await db.getModelPointcloudFileById(pointcloud.sourceModelPointcloudId);
|
||||
if (!source || source.inspectionObjectId !== task.inspectionObjectId) {
|
||||
throw new TRPCError({ code: "BAD_REQUEST", message: "点云与当前检测对象不兼容。" });
|
||||
}
|
||||
}
|
||||
if (cad.source === "model_library" && cad.sourceModelCadFileId) {
|
||||
if (!await db.cadAppliesToInspectionObject(cad.sourceModelCadFileId, task.inspectionObjectId)) {
|
||||
throw new TRPCError({ code: "BAD_REQUEST", message: "CAD 与当前检测对象不兼容。" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function latestRegisteredPointcloud(
|
||||
pointcloudFiles: Awaited<ReturnType<typeof db.listTaskPointcloudFiles>>,
|
||||
sourcePointcloudFileId: number,
|
||||
@@ -581,16 +605,88 @@ export const appRouter = router({
|
||||
}),
|
||||
}),
|
||||
|
||||
// ─── 检测对象配置 ─────────────────────────────────────────────────────────
|
||||
inspectionObject: router({
|
||||
list: protectedProcedure
|
||||
.input(z.object({ modelId: z.number(), activeOnly: z.boolean().optional() }))
|
||||
.query(({ input }) => db.listInspectionObjects(input.modelId, input.activeOnly ?? false)),
|
||||
create: adminProcedure
|
||||
.input(z.object({ modelId: z.number(), name: z.string().trim().min(1).max(100), description: z.string().optional() }))
|
||||
.mutation(({ input }) => db.createInspectionObject({ ...input, description: input.description ?? null, status: "draft" })),
|
||||
update: adminProcedure
|
||||
.input(z.object({ id: z.number(), name: z.string().trim().min(1).max(100).optional(), description: z.string().optional() }))
|
||||
.mutation(({ input }) => {
|
||||
const { id, ...data } = input;
|
||||
return db.updateInspectionObject(id, data);
|
||||
}),
|
||||
configureDefaults: adminProcedure
|
||||
.input(z.object({ id: z.number(), defaultModelPointcloudId: z.number(), defaultModelCadFileId: z.number(), active: z.boolean() }))
|
||||
.mutation(async ({ input }) => {
|
||||
try { return await db.configureInspectionObject(input); }
|
||||
catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error.message : "默认输入方案无效" }); }
|
||||
}),
|
||||
setStatus: adminProcedure
|
||||
.input(z.object({ id: z.number(), status: z.enum(["draft", "active"]) }))
|
||||
.mutation(async ({ input }) => {
|
||||
if (input.status === "active") {
|
||||
const object = await db.getInspectionObjectById(input.id);
|
||||
if (!object?.defaultModelPointcloudId || !object.defaultModelCadFileId) {
|
||||
throw new TRPCError({ code: "PRECONDITION_FAILED", message: "缺少预置点云或默认 CAD,不能启用检测对象" });
|
||||
}
|
||||
return db.configureInspectionObject({
|
||||
id: object.id,
|
||||
defaultModelPointcloudId: object.defaultModelPointcloudId,
|
||||
defaultModelCadFileId: object.defaultModelCadFileId,
|
||||
active: true,
|
||||
});
|
||||
}
|
||||
return db.updateInspectionObject(input.id, { status: "draft" });
|
||||
}),
|
||||
delete: adminProcedure
|
||||
.input(z.object({ id: z.number() }))
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
await db.deleteInspectionObject(input.id);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
if (error instanceof db.InspectionObjectReferencedError) {
|
||||
throw new TRPCError({ code: "PRECONDITION_FAILED", message: error.message });
|
||||
}
|
||||
if (error instanceof Error && error.message === "检测对象不存在") {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: error.message });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
}),
|
||||
|
||||
// ─── 车型 CAD 文件 ─────────────────────────────────────────────────────────
|
||||
|
||||
cadFile: router({
|
||||
list: protectedProcedure
|
||||
.input(z.object({ modelId: z.number() }))
|
||||
.query(async ({ input }) => {
|
||||
return db.listModelCadFiles(input.modelId);
|
||||
const files = await db.listModelCadFiles(input.modelId);
|
||||
const objectIds = await db.listCadInspectionObjectIds(files.map(file => file.id));
|
||||
return files.map(file => ({ ...file, inspectionObjectIds: objectIds[file.id] ?? [] }));
|
||||
}),
|
||||
|
||||
delete: protectedProcedure
|
||||
listForInspectionObject: protectedProcedure
|
||||
.input(z.object({ modelId: z.number(), inspectionObjectId: z.number() }))
|
||||
.query(({ input }) => db.listModelCadFilesForInspectionObject(input.modelId, input.inspectionObjectId)),
|
||||
|
||||
setInspectionObjects: adminProcedure
|
||||
.input(z.object({ id: z.number(), inspectionObjectIds: z.array(z.number()).min(1) }))
|
||||
.mutation(async ({ input }) => {
|
||||
const cad = await db.getModelCadFileById(input.id);
|
||||
if (!cad) throw new TRPCError({ code: "NOT_FOUND", message: "CAD 文件不存在" });
|
||||
const objects = await Promise.all(input.inspectionObjectIds.map(id => db.getInspectionObjectById(id)));
|
||||
if (objects.some(object => !object || object.modelId !== cad.modelId)) throw new TRPCError({ code: "BAD_REQUEST", message: "适用检测对象必须属于 CAD 当前车型" });
|
||||
await db.setCadInspectionObjects(input.id, input.inspectionObjectIds);
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
delete: adminProcedure
|
||||
.input(z.object({ id: z.number() }))
|
||||
.mutation(async ({ input }) => {
|
||||
await db.deleteModelCadFile(input.id);
|
||||
@@ -623,6 +719,14 @@ export const appRouter = router({
|
||||
return db.listModelPointcloudFiles(input.modelId);
|
||||
}),
|
||||
|
||||
listForInspectionObject: protectedProcedure
|
||||
.input(z.object({ modelId: z.number(), inspectionObjectId: z.number() }))
|
||||
.query(({ input }) => db.listModelPointcloudFilesForInspectionObject(input.modelId, input.inspectionObjectId)),
|
||||
|
||||
assignInspectionObject: adminProcedure
|
||||
.input(z.object({ id: z.number(), inspectionObjectId: z.number() }))
|
||||
.mutation(({ input }) => db.assignModelPointcloudInspectionObject(input.id, input.inspectionObjectId)),
|
||||
|
||||
rename: adminProcedure
|
||||
.input(z.object({ id: z.number(), baseName: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
@@ -640,7 +744,7 @@ export const appRouter = router({
|
||||
return db.updateModelPointcloudFileName(row.id, name);
|
||||
}),
|
||||
|
||||
delete: protectedProcedure
|
||||
delete: adminProcedure
|
||||
.input(z.object({ id: z.number() }))
|
||||
.mutation(async ({ input }) => {
|
||||
await db.deleteModelPointcloudFile(input.id);
|
||||
@@ -720,9 +824,23 @@ export const appRouter = router({
|
||||
return db.getInspectionTaskById(input.id);
|
||||
}),
|
||||
|
||||
classify: adminProcedure
|
||||
.input(z.object({ id: z.number(), inspectionObjectId: z.number() }))
|
||||
.mutation(async ({ input }) => {
|
||||
const task = await db.getInspectionTaskById(input.id);
|
||||
const object = await db.getInspectionObjectById(input.inspectionObjectId);
|
||||
if (!task || !object) throw new TRPCError({ code: "NOT_FOUND", message: "任务或检测对象不存在" });
|
||||
if (task.modelId !== object.modelId || object.status !== "active") {
|
||||
throw new TRPCError({ code: "BAD_REQUEST", message: "只能确认当前车型下已启用的检测对象" });
|
||||
}
|
||||
await db.classifyInspectionTask(task.id, object.id);
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
create: protectedProcedure
|
||||
.input(z.object({
|
||||
modelId: z.number(),
|
||||
inspectionObjectId: z.number(),
|
||||
name: z.string().trim().min(1).max(255),
|
||||
inspector: z.string().max(100).optional(),
|
||||
notes: z.string().optional(),
|
||||
@@ -734,8 +852,9 @@ export const appRouter = router({
|
||||
}
|
||||
|
||||
try {
|
||||
return await db.createInspectionTask({
|
||||
return await db.createInspectionTaskWithDefaults({
|
||||
modelId: input.modelId,
|
||||
inspectionObjectId: input.inspectionObjectId,
|
||||
name: input.name,
|
||||
inspector: input.inspector ?? null,
|
||||
notes: input.notes ?? null,
|
||||
@@ -1212,6 +1331,7 @@ export const appRouter = router({
|
||||
if (!cad) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "选中的 CAD 文件不存在。" });
|
||||
}
|
||||
await validateTaskInputCompatibility(input.taskId, sourcePointcloud, cad);
|
||||
|
||||
const registration = await createAndEnqueuePluginJob({
|
||||
taskId: input.taskId,
|
||||
|
||||
Reference in New Issue
Block a user