This commit is contained in:
phaichayon
2026-06-26 12:04:17 +07:00
parent 052677fa2c
commit 2ad57f0ad9
26 changed files with 2653 additions and 250 deletions

View File

@@ -0,0 +1,46 @@
import { NextRequest, NextResponse } from 'next/server';
import { auditAction } from '@/features/foundation/audit-log/service';
import {
activateApprovalWorkflow,
getApprovalWorkflow
} from '@/features/crm/approval/server/workflow-builder.service';
import { PERMISSIONS } from '@/lib/auth/rbac';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
type Params = {
params: Promise<{ id: string }>;
};
export async function POST(_request: NextRequest, { params }: Params) {
try {
const { id } = await params;
const { organization, session } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowActivate
});
const before = await getApprovalWorkflow(id, organization.id);
const updated = await activateApprovalWorkflow(id, organization.id, session.user.id);
await auditAction({
organizationId: organization.id,
userId: session.user.id,
entityType: 'crm_approval_workflow',
entityId: id,
action: 'activate_approval_workflow',
beforeData: before,
afterData: updated
});
return NextResponse.json({
success: true,
message: 'Approval workflow activated successfully'
});
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: 'Unable activate approval workflow' }, { status: 500 });
}
}

View File

@@ -0,0 +1,43 @@
import { NextRequest, NextResponse } from 'next/server';
import { auditAction } from '@/features/foundation/audit-log/service';
import { cloneApprovalWorkflow, getApprovalWorkflow } from '@/features/crm/approval/server/workflow-builder.service';
import { PERMISSIONS } from '@/lib/auth/rbac';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
type Params = {
params: Promise<{ id: string }>;
};
export async function POST(_request: NextRequest, { params }: Params) {
try {
const { id } = await params;
const { organization, session } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowClone
});
const before = await getApprovalWorkflow(id, organization.id);
const cloned = await cloneApprovalWorkflow(id, organization.id, session.user.id);
await auditAction({
organizationId: organization.id,
userId: session.user.id,
entityType: 'crm_approval_workflow',
entityId: cloned.id,
action: 'clone_approval_workflow',
beforeData: before,
afterData: cloned
});
return NextResponse.json({
success: true,
message: 'Approval workflow cloned successfully'
});
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: 'Unable clone approval workflow' }, { status: 500 });
}
}

View File

@@ -0,0 +1,46 @@
import { NextRequest, NextResponse } from 'next/server';
import { auditAction } from '@/features/foundation/audit-log/service';
import {
deactivateApprovalWorkflow,
getApprovalWorkflow
} from '@/features/crm/approval/server/workflow-builder.service';
import { PERMISSIONS } from '@/lib/auth/rbac';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
type Params = {
params: Promise<{ id: string }>;
};
export async function POST(_request: NextRequest, { params }: Params) {
try {
const { id } = await params;
const { organization, session } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowDeactivate
});
const before = await getApprovalWorkflow(id, organization.id);
const updated = await deactivateApprovalWorkflow(id, organization.id, session.user.id);
await auditAction({
organizationId: organization.id,
userId: session.user.id,
entityType: 'crm_approval_workflow',
entityId: id,
action: 'deactivate_approval_workflow',
beforeData: before,
afterData: updated
});
return NextResponse.json({
success: true,
message: 'Approval workflow deactivated successfully'
});
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: 'Unable deactivate approval workflow' }, { status: 500 });
}
}

View File

@@ -1,11 +1,12 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { auditDelete, auditUpdate } from '@/features/foundation/audit-log/service';
import { auditAction } from '@/features/foundation/audit-log/service';
import { updateApprovalWorkflowSchema } from '@/features/crm/approval/schemas/approval-workflow.schema';
import {
getApprovalWorkflow,
softDeleteApprovalWorkflow,
updateApprovalWorkflow
} from '@/features/foundation/approval/server/service';
} from '@/features/crm/approval/server/workflow-builder.service';
import { PERMISSIONS } from '@/lib/auth/rbac';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
@@ -13,13 +14,6 @@ type Params = {
params: Promise<{ id: string }>;
};
const workflowSchema = z.object({
code: z.string().min(1).optional(),
name: z.string().min(1).optional(),
entityType: z.string().min(1).optional(),
isActive: z.boolean().optional()
});
export async function GET(_request: NextRequest, { params }: Params) {
try {
const { id } = await params;
@@ -41,7 +35,7 @@ export async function GET(_request: NextRequest, { params }: Params) {
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: 'Unable to load approval workflow' }, { status: 500 });
return NextResponse.json({ message: 'Unable load approval workflow' }, { status: 500 });
}
}
@@ -51,15 +45,16 @@ export async function PATCH(request: NextRequest, { params }: Params) {
const { organization, session } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowUpdate
});
const payload = workflowSchema.parse(await request.json());
const payload = updateApprovalWorkflowSchema.parse(await request.json());
const before = await getApprovalWorkflow(id, organization.id);
const updated = await updateApprovalWorkflow(id, organization.id, payload);
const updated = await updateApprovalWorkflow(id, organization.id, session.user.id, payload);
await auditUpdate({
await auditAction({
organizationId: organization.id,
userId: session.user.id,
entityType: 'crm_approval_workflow',
entityId: id,
action: 'update_approval_workflow',
beforeData: before,
afterData: updated
});
@@ -81,7 +76,7 @@ export async function PATCH(request: NextRequest, { params }: Params) {
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: 'Unable to update approval workflow' }, { status: 500 });
return NextResponse.json({ message: 'Unable update approval workflow' }, { status: 500 });
}
}
@@ -91,13 +86,14 @@ export async function DELETE(_request: NextRequest, { params }: Params) {
const { organization, session } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowDelete
});
const result = await softDeleteApprovalWorkflow(id, organization.id);
const result = await softDeleteApprovalWorkflow(id, organization.id, session.user.id);
await auditDelete({
await auditAction({
organizationId: organization.id,
userId: session.user.id,
entityType: 'crm_approval_workflow',
entityId: id,
action: 'delete_approval_workflow',
beforeData: result.before,
afterData: result.after
});
@@ -113,6 +109,6 @@ export async function DELETE(_request: NextRequest, { params }: Params) {
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: 'Unable to delete approval workflow' }, { status: 500 });
return NextResponse.json({ message: 'Unable delete approval workflow' }, { status: 500 });
}
}

View File

@@ -0,0 +1,90 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { auditAction } from '@/features/foundation/audit-log/service';
import { updateApprovalStepSchema } from '@/features/crm/approval/schemas/approval-step.schema';
import {
deleteApprovalWorkflowStep,
getApprovalWorkflow,
updateApprovalWorkflowStep
} from '@/features/crm/approval/server/workflow-builder.service';
import { PERMISSIONS } from '@/lib/auth/rbac';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
type Params = {
params: Promise<{ id: string; stepId: string }>;
};
export async function PATCH(request: NextRequest, { params }: Params) {
try {
const { id, stepId } = await params;
const { organization, session } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowStepManage
});
const payload = updateApprovalStepSchema.parse(await request.json());
const before = await getApprovalWorkflow(id, organization.id);
const updated = await updateApprovalWorkflowStep(stepId, organization.id, payload);
await auditAction({
organizationId: organization.id,
userId: session.user.id,
entityType: 'crm_approval_step',
entityId: stepId,
action: 'update_approval_step',
beforeData: before.steps,
afterData: updated
});
return NextResponse.json({
success: true,
message: 'Approval workflow step updated successfully'
});
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof z.ZodError) {
return NextResponse.json(
{ message: error.issues[0]?.message ?? 'Invalid payload' },
{ status: 400 }
);
}
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: 'Unable update approval workflow step' }, { status: 500 });
}
}
export async function DELETE(_request: NextRequest, { params }: Params) {
try {
const { id, stepId } = await params;
const { organization, session } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowStepManage
});
const before = await getApprovalWorkflow(id, organization.id);
const result = await deleteApprovalWorkflowStep(stepId, organization.id);
await auditAction({
organizationId: organization.id,
userId: session.user.id,
entityType: 'crm_approval_step',
entityId: stepId,
action: 'delete_approval_step',
beforeData: before.steps,
afterData: result.steps
});
return NextResponse.json({
success: true,
message: 'Approval workflow step deleted successfully'
});
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: 'Unable delete approval workflow step' }, { status: 500 });
}
}

View File

@@ -0,0 +1,55 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { auditAction } from '@/features/foundation/audit-log/service';
import { reorderApprovalStepsSchema } from '@/features/crm/approval/schemas/approval-step.schema';
import {
getApprovalWorkflow,
reorderApprovalWorkflowSteps
} from '@/features/crm/approval/server/workflow-builder.service';
import { PERMISSIONS } from '@/lib/auth/rbac';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
type Params = {
params: Promise<{ id: string }>;
};
export async function POST(request: NextRequest, { params }: Params) {
try {
const { id } = await params;
const { organization, session } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowStepManage
});
const payload = reorderApprovalStepsSchema.parse(await request.json());
const before = await getApprovalWorkflow(id, organization.id);
const steps = await reorderApprovalWorkflowSteps(id, organization.id, payload);
await auditAction({
organizationId: organization.id,
userId: session.user.id,
entityType: 'crm_approval_step',
entityId: id,
action: 'reorder_approval_steps',
beforeData: before.steps,
afterData: steps
});
return NextResponse.json({
success: true,
message: 'Approval workflow steps reordered successfully'
});
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof z.ZodError) {
return NextResponse.json(
{ message: error.issues[0]?.message ?? 'Invalid payload' },
{ status: 400 }
);
}
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: 'Unable reorder approval workflow steps' }, { status: 500 });
}
}

View File

@@ -1,50 +1,67 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { auditUpdate } from '@/features/foundation/audit-log/service';
import { auditAction } from '@/features/foundation/audit-log/service';
import { createApprovalStepSchema } from '@/features/crm/approval/schemas/approval-step.schema';
import {
createApprovalWorkflowStep,
getApprovalWorkflow,
replaceApprovalWorkflowSteps
} from '@/features/foundation/approval/server/service';
import { BUSINESS_ROLES, PERMISSIONS } from '@/lib/auth/rbac';
listApprovalWorkflowSteps
} from '@/features/crm/approval/server/workflow-builder.service';
import { PERMISSIONS } from '@/lib/auth/rbac';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
type Params = {
params: Promise<{ id: string }>;
};
const stepSchema = z.object({
stepNumber: z.number().int().min(1),
roleCode: z.enum(BUSINESS_ROLES),
roleName: z.string().min(1),
isRequired: z.boolean().optional()
});
export async function GET(_request: NextRequest, { params }: Params) {
try {
const { id } = await params;
const { organization } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowRead
});
const items = await listApprovalWorkflowSteps(id, organization.id);
const payloadSchema = z.object({
steps: z.array(stepSchema)
});
return NextResponse.json({
success: true,
time: new Date().toISOString(),
message: 'Approval workflow steps loaded successfully',
items
});
} catch (error) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: 'Unable load approval workflow steps' }, { status: 500 });
}
}
export async function PUT(request: NextRequest, { params }: Params) {
export async function POST(request: NextRequest, { params }: Params) {
try {
const { id } = await params;
const { organization, session } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowUpdate
permission: PERMISSIONS.crmApprovalWorkflowStepManage
});
const payload = payloadSchema.parse(await request.json());
const payload = createApprovalStepSchema.parse(await request.json());
const before = await getApprovalWorkflow(id, organization.id);
const steps = await replaceApprovalWorkflowSteps(id, organization.id, payload.steps);
const steps = await createApprovalWorkflowStep(id, organization.id, payload);
await auditUpdate({
await auditAction({
organizationId: organization.id,
userId: session.user.id,
entityType: 'crm_approval_step',
entityId: id,
action: 'create_approval_step',
beforeData: before.steps,
afterData: steps
});
return NextResponse.json({
success: true,
message: 'Approval workflow steps updated successfully'
message: 'Approval workflow step created successfully'
});
} catch (error) {
if (error instanceof AuthError) {
@@ -59,9 +76,6 @@ export async function PUT(request: NextRequest, { params }: Params) {
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json(
{ message: 'Unable to update approval workflow steps' },
{ status: 500 }
);
return NextResponse.json({ message: 'Unable create approval workflow step' }, { status: 500 });
}
}

View File

@@ -1,26 +1,24 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { auditCreate } from '@/features/foundation/audit-log/service';
import { auditAction } from '@/features/foundation/audit-log/service';
import { createApprovalWorkflowSchema, approvalWorkflowFilterSchema } from '@/features/crm/approval/schemas/approval-workflow.schema';
import {
createApprovalWorkflow,
listApprovalWorkflows
} from '@/features/foundation/approval/server/service';
import { BUSINESS_ROLES, PERMISSIONS } from '@/lib/auth/rbac';
} from '@/features/crm/approval/server/workflow-builder.service';
import { PERMISSIONS } from '@/lib/auth/rbac';
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
const workflowSchema = z.object({
code: z.string().min(1),
name: z.string().min(1),
entityType: z.string().min(1),
isActive: z.boolean().optional()
});
export async function GET(_request: NextRequest) {
export async function GET(request: NextRequest) {
try {
const { organization } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowRead
});
const items = await listApprovalWorkflows(organization.id);
const filters = approvalWorkflowFilterSchema.parse({
entityType: request.nextUrl.searchParams.get('entityType') ?? undefined,
activeOnly: request.nextUrl.searchParams.get('activeOnly') ?? undefined
});
const items = await listApprovalWorkflows(organization.id, filters);
return NextResponse.json({
success: true,
@@ -32,10 +30,16 @@ export async function GET(_request: NextRequest) {
if (error instanceof AuthError) {
return NextResponse.json({ message: error.message }, { status: error.status });
}
if (error instanceof z.ZodError) {
return NextResponse.json(
{ message: error.issues[0]?.message ?? 'Invalid filters' },
{ status: 400 }
);
}
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: 'Unable to load approval workflows' }, { status: 500 });
return NextResponse.json({ message: 'Unable load approval workflows' }, { status: 500 });
}
}
@@ -44,21 +48,21 @@ export async function POST(request: NextRequest) {
const { organization, session } = await requireOrganizationAccess({
permission: PERMISSIONS.crmApprovalWorkflowCreate
});
const payload = workflowSchema.parse(await request.json());
const created = await createApprovalWorkflow(organization.id, payload);
const payload = createApprovalWorkflowSchema.parse(await request.json());
const created = await createApprovalWorkflow(organization.id, session.user.id, payload);
await auditCreate({
await auditAction({
organizationId: organization.id,
userId: session.user.id,
entityType: 'crm_approval_workflow',
entityId: created.id,
action: 'create_approval_workflow',
afterData: created
});
return NextResponse.json({
success: true,
message: 'Approval workflow created successfully',
allowedRoles: BUSINESS_ROLES
message: 'Approval workflow created successfully'
});
} catch (error) {
if (error instanceof AuthError) {
@@ -73,6 +77,6 @@ export async function POST(request: NextRequest) {
if (error instanceof Error) {
return NextResponse.json({ message: error.message }, { status: 400 });
}
return NextResponse.json({ message: 'Unable to create approval workflow' }, { status: 500 });
return NextResponse.json({ message: 'Unable create approval workflow' }, { status: 500 });
}
}