feat: 完成错误原因合同与付款就绪提示(09-05-error-explainability-payment-readiness)
这个提交包含在:
@@ -1,3 +1,4 @@
|
||||
const { buildBusinessValidationError } = require("../utils/businessValidationError");
|
||||
const { z } = require("zod");
|
||||
|
||||
const { hasPermission } = require("../constants/permissions");
|
||||
@@ -60,19 +61,12 @@ const LIST_QUERY_SCHEMA = z.object({
|
||||
pageSize: z.coerce.number().int().min(1).max(100).optional(),
|
||||
});
|
||||
|
||||
function buildValidationError(details, statusCode = 400) {
|
||||
return new AppError({
|
||||
code: "VALID_001",
|
||||
message: "参数校验失败",
|
||||
statusCode,
|
||||
details,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function parseInput(schema, value) {
|
||||
const parsed = schema.safeParse(value ?? {});
|
||||
if (!parsed.success) {
|
||||
throw buildValidationError(parsed.error.flatten());
|
||||
throw buildBusinessValidationError("attendance_correction.invalid_input", parsed.error.flatten(), 400);
|
||||
}
|
||||
return parsed.data;
|
||||
}
|
||||
@@ -80,7 +74,7 @@ function parseInput(schema, value) {
|
||||
function parseUuid(value, key = "id") {
|
||||
const parsed = UUID_SCHEMA.safeParse(value);
|
||||
if (!parsed.success) {
|
||||
throw buildValidationError({ [key]: "invalid_uuid" });
|
||||
throw buildBusinessValidationError("attendance_correction.invalid_uuid", { [key]: "invalid_uuid" }, 400);
|
||||
}
|
||||
return parsed.data;
|
||||
}
|
||||
@@ -93,7 +87,7 @@ function parseOptionalDateTime(value) {
|
||||
if (!value) return null;
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
throw buildValidationError({ datetime: "invalid" });
|
||||
throw buildBusinessValidationError("attendance_correction.datetime_invalid", { datetime: "invalid" }, 400);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
@@ -119,10 +113,7 @@ async function ensureUnifiedFeatureEnabled(fastify, request) {
|
||||
strictDepartment: false,
|
||||
});
|
||||
if (!gate.enabled) {
|
||||
throw buildValidationError(
|
||||
{ featureFlag: "attendance_unified_v1_disabled", reason: gate.reason },
|
||||
409,
|
||||
);
|
||||
throw buildBusinessValidationError("attendance_correction.feature_flag_attendance_unified_v1_disabled", { featureFlag: "attendance_unified_v1_disabled", reason: gate.reason }, 409);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +280,7 @@ async function attendanceCorrectionsRoutes(fastify) {
|
||||
select: { id: true, definition_json: true },
|
||||
});
|
||||
if (!workflowDefinition) {
|
||||
throw buildValidationError({ workflow: "missing_definition" }, 409);
|
||||
throw buildBusinessValidationError("attendance_correction.workflow_missing_definition", { workflow: "missing_definition" }, 409);
|
||||
}
|
||||
const workflowSteps = parseWorkflowSteps(workflowDefinition.definition_json);
|
||||
const { actionable, skipped } = await resolveActionableStep({
|
||||
@@ -301,7 +292,7 @@ async function attendanceCorrectionsRoutes(fastify) {
|
||||
blockOnUnresolvedAssignee: true,
|
||||
});
|
||||
if (!actionable) {
|
||||
throw buildValidationError({ workflow: "no_actionable_approver" }, 409);
|
||||
throw buildBusinessValidationError("attendance_correction.workflow_no_actionable_approver", { workflow: "no_actionable_approver" }, 409);
|
||||
}
|
||||
|
||||
const workflowInstance = await tx.workflowInstance.create({
|
||||
@@ -512,7 +503,7 @@ async function attendanceCorrectionsRoutes(fastify) {
|
||||
throw new AppError({ code: "PERM_001", message: "无权限", statusCode: 403 });
|
||||
}
|
||||
if (correction.status !== "reviewing") {
|
||||
throw buildValidationError({ status: "invalid_cancel_status" }, 409);
|
||||
throw buildBusinessValidationError("attendance_correction.status_invalid_cancel_status", { status: "invalid_cancel_status" }, 409);
|
||||
}
|
||||
|
||||
const workflowInstance = await tx.workflowInstance.findFirst({
|
||||
|
||||
在新工单中引用
屏蔽一个用户