task h.1 adn fix permission
This commit is contained in:
6
src/app/api/crm/enquiries/[id]/_schemas.ts
Normal file
6
src/app/api/crm/enquiries/[id]/_schemas.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const enquiryAssignmentRequestSchema = z.object({
|
||||
assignedToUserId: z.string().min(1, 'Sales user is required'),
|
||||
remark: z.string().trim().max(1000).optional()
|
||||
});
|
||||
62
src/app/api/crm/enquiries/[id]/assign/route.ts
Normal file
62
src/app/api/crm/enquiries/[id]/assign/route.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { z } from 'zod';
|
||||
import { auditAction } from '@/features/foundation/audit-log/service';
|
||||
import { assignEnquiryToUser, getEnquiryDetail } from '@/features/crm/enquiries/server/service';
|
||||
import { PERMISSIONS } from '@/lib/auth/rbac';
|
||||
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
|
||||
import { enquiryAssignmentRequestSchema } from '../_schemas';
|
||||
|
||||
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.crmEnquiryAssign
|
||||
});
|
||||
const payload = enquiryAssignmentRequestSchema.parse(await request.json());
|
||||
const before = await getEnquiryDetail(id, organization.id);
|
||||
const updated = await assignEnquiryToUser(id, organization.id, session.user.id, payload);
|
||||
|
||||
await auditAction({
|
||||
organizationId: organization.id,
|
||||
branchId: updated.branchId,
|
||||
userId: session.user.id,
|
||||
entityType: 'crm_enquiry',
|
||||
entityId: id,
|
||||
action: 'assign',
|
||||
beforeData: {
|
||||
oldAssignedToUserId: before.assignedToUserId
|
||||
},
|
||||
afterData: {
|
||||
newAssignedToUserId: updated.assignedToUserId,
|
||||
remark: updated.assignmentRemark
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Enquiry assigned successfully',
|
||||
enquiry: updated
|
||||
});
|
||||
} 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 to assign enquiry' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
62
src/app/api/crm/enquiries/[id]/reassign/route.ts
Normal file
62
src/app/api/crm/enquiries/[id]/reassign/route.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { z } from 'zod';
|
||||
import { auditAction } from '@/features/foundation/audit-log/service';
|
||||
import { getEnquiryDetail, reassignEnquiryToUser } from '@/features/crm/enquiries/server/service';
|
||||
import { PERMISSIONS } from '@/lib/auth/rbac';
|
||||
import { AuthError, requireOrganizationAccess } from '@/lib/auth/session';
|
||||
import { enquiryAssignmentRequestSchema } from '../_schemas';
|
||||
|
||||
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.crmEnquiryReassign
|
||||
});
|
||||
const payload = enquiryAssignmentRequestSchema.parse(await request.json());
|
||||
const before = await getEnquiryDetail(id, organization.id);
|
||||
const updated = await reassignEnquiryToUser(id, organization.id, session.user.id, payload);
|
||||
|
||||
await auditAction({
|
||||
organizationId: organization.id,
|
||||
branchId: updated.branchId,
|
||||
userId: session.user.id,
|
||||
entityType: 'crm_enquiry',
|
||||
entityId: id,
|
||||
action: 'reassign',
|
||||
beforeData: {
|
||||
oldAssignedToUserId: before.assignedToUserId
|
||||
},
|
||||
afterData: {
|
||||
newAssignedToUserId: updated.assignedToUserId,
|
||||
remark: updated.assignmentRemark
|
||||
}
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Enquiry reassigned successfully',
|
||||
enquiry: updated
|
||||
});
|
||||
} 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 to reassign enquiry' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ export async function GET(request: NextRequest) {
|
||||
plan: organization.plan,
|
||||
imageUrl: organization.imageUrl,
|
||||
role: 'admin',
|
||||
businessRole: 'it_admin',
|
||||
businessRole: 'sales_manager',
|
||||
canManageUsers: true
|
||||
}))
|
||||
});
|
||||
@@ -51,7 +51,12 @@ export async function GET(request: NextRequest) {
|
||||
const organizationRows = await db
|
||||
.select()
|
||||
.from(organizations)
|
||||
.where(inArray(organizations.id, filteredMemberships.map((membership) => membership.organizationId)));
|
||||
.where(
|
||||
inArray(
|
||||
organizations.id,
|
||||
filteredMemberships.map((membership) => membership.organizationId)
|
||||
)
|
||||
);
|
||||
|
||||
const membershipMap = new Map(
|
||||
filteredMemberships.map((membership) => [membership.organizationId, membership])
|
||||
@@ -68,7 +73,7 @@ export async function GET(request: NextRequest) {
|
||||
plan: organization.plan,
|
||||
imageUrl: organization.imageUrl,
|
||||
role: membership?.role ?? 'user',
|
||||
businessRole: membership?.businessRole ?? 'viewer',
|
||||
businessRole: membership?.businessRole ?? 'sales_support',
|
||||
canManageUsers: membership?.role === 'admin'
|
||||
};
|
||||
})
|
||||
@@ -109,8 +114,8 @@ export async function POST(request: NextRequest) {
|
||||
userId: session.user.id,
|
||||
organizationId,
|
||||
role: 'admin',
|
||||
businessRole: 'it_admin',
|
||||
permissions: getDefaultPermissions('admin', 'it_admin')
|
||||
businessRole: 'sales_manager',
|
||||
permissions: getDefaultPermissions('admin', 'sales_manager')
|
||||
});
|
||||
|
||||
await db
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { eq, inArray } from 'drizzle-orm';
|
||||
import { memberships, organizations, users } from '@/db/schema';
|
||||
import {
|
||||
type BusinessRole,
|
||||
getDefaultBusinessRole,
|
||||
getDefaultPermissions,
|
||||
isBusinessRole,
|
||||
@@ -13,7 +14,7 @@ import { db } from '@/lib/db';
|
||||
export type UserMembershipInput = {
|
||||
organizationId: string;
|
||||
membershipRole: 'admin' | 'user';
|
||||
businessRole: 'it_admin' | 'helpdesk' | 'infrastructure' | 'application' | 'auditor' | 'viewer';
|
||||
businessRole: BusinessRole;
|
||||
};
|
||||
|
||||
export type UserMutationInput = {
|
||||
@@ -81,8 +82,7 @@ export function parseUserMutationPayload(body: unknown): UserMutationInput {
|
||||
const value = membership as Record<string, unknown>;
|
||||
const organizationId =
|
||||
typeof value.organizationId === 'string' ? value.organizationId.trim() : '';
|
||||
const membershipRole =
|
||||
typeof value.membershipRole === 'string' ? value.membershipRole : '';
|
||||
const membershipRole = typeof value.membershipRole === 'string' ? value.membershipRole : '';
|
||||
const businessRole =
|
||||
typeof value.businessRole === 'string'
|
||||
? value.businessRole
|
||||
@@ -102,7 +102,9 @@ export function parseUserMutationPayload(body: unknown): UserMutationInput {
|
||||
})
|
||||
.filter((membership): membership is UserMembershipInput => membership !== null);
|
||||
|
||||
const uniqueMemberships = [...new Map(memberships.map((membership) => [membership.organizationId, membership])).values()];
|
||||
const uniqueMemberships = [
|
||||
...new Map(memberships.map((membership) => [membership.organizationId, membership])).values()
|
||||
];
|
||||
|
||||
if (!uniqueMemberships.length) {
|
||||
throw new Error('Select at least one organization');
|
||||
|
||||
@@ -3,6 +3,7 @@ import { asc, eq } from 'drizzle-orm';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { memberships, organizations, users } from '@/db/schema';
|
||||
import { db } from '@/lib/db';
|
||||
import { isBusinessRole } from '@/lib/auth/rbac';
|
||||
import { AuthError } from '@/lib/auth/session';
|
||||
import {
|
||||
buildMembershipValues,
|
||||
@@ -38,13 +39,7 @@ function formatUsers(rows: UserRow[]) {
|
||||
organizationId: string;
|
||||
organizationName: string;
|
||||
membershipRole: 'admin' | 'user';
|
||||
businessRole:
|
||||
| 'it_admin'
|
||||
| 'helpdesk'
|
||||
| 'infrastructure'
|
||||
| 'application'
|
||||
| 'auditor'
|
||||
| 'viewer';
|
||||
businessRole: string;
|
||||
}>;
|
||||
}
|
||||
>();
|
||||
@@ -60,14 +55,7 @@ function formatUsers(rows: UserRow[]) {
|
||||
organizationId: row.organizationId,
|
||||
organizationName: row.organizationName,
|
||||
membershipRole: organization.role,
|
||||
businessRole:
|
||||
row.businessRole === 'it_admin' ||
|
||||
row.businessRole === 'helpdesk' ||
|
||||
row.businessRole === 'infrastructure' ||
|
||||
row.businessRole === 'application' ||
|
||||
row.businessRole === 'auditor'
|
||||
? row.businessRole
|
||||
: 'viewer'
|
||||
businessRole: isBusinessRole(row.businessRole) ? row.businessRole : 'sales_support'
|
||||
} as const;
|
||||
|
||||
if (existing) {
|
||||
@@ -146,13 +134,13 @@ function sortUsers(usersList: ReturnType<typeof formatUsers>, sort?: string) {
|
||||
? `${a.systemRole}:${a.activeMembershipRole ?? ''}`
|
||||
: primarySort.id === 'organizations'
|
||||
? a.memberships.map((membership) => membership.organizationName).join(', ')
|
||||
: a[primarySort.id as 'name' | 'email'] ?? a.name;
|
||||
: (a[primarySort.id as 'name' | 'email'] ?? a.name);
|
||||
const right =
|
||||
primarySort.id === 'role'
|
||||
? `${b.systemRole}:${b.activeMembershipRole ?? ''}`
|
||||
: primarySort.id === 'organizations'
|
||||
? b.memberships.map((membership) => membership.organizationName).join(', ')
|
||||
: b[primarySort.id as 'name' | 'email'] ?? b.name;
|
||||
: (b[primarySort.id as 'name' | 'email'] ?? b.name);
|
||||
|
||||
return String(left).localeCompare(String(right));
|
||||
});
|
||||
@@ -244,7 +232,11 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
await validateManagedOrganizations(payload.memberships, manageableOrganizationIds, isSuperAdmin);
|
||||
await validateManagedOrganizations(
|
||||
payload.memberships,
|
||||
manageableOrganizationIds,
|
||||
isSuperAdmin
|
||||
);
|
||||
|
||||
const existingUser = await db.query.users.findFirst({
|
||||
where: eq(users.email, payload.email)
|
||||
|
||||
Reference in New Issue
Block a user