Skip to content
Merged
3 changes: 2 additions & 1 deletion apps/app/languine.lock
Original file line number Diff line number Diff line change
Expand Up @@ -402,9 +402,10 @@ files:
people.filters.role: 4a49167c423fab80b22db2b551813a81
people.actions.invite: 3aa4cd2c53d3d24b465398053e12fe65
people.actions.clear: 0b275442d6556cff30b75f37f1918899
people.table.name: 49ee3087348e8d44e1feda1917443987
people.table.name: 717231b5b2cf0543d46d6555f25c56b4
people.table.email: ce8ae9da5b7cd6c3df2929543a9af92d
people.table.department: 1d17cb9923b99f823da9f5a16dc460e5
people.table.status: ec53a8c4f07baed5d8825072c89799be
people.table.externalId: 01ddb49044c161b11fdb0c41adbeb3b3
people.empty.no_employees.title: 9047cc33a16248133c3fcc8884523588
people.empty.no_employees.description: 3fdb36a3d56c06043da562aaa1045825
Expand Down
132 changes: 9 additions & 123 deletions apps/app/src/actions/people/create-employee-action.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,10 @@
"use server";

import { type Employee, db } from "@bubba/db";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
import { authActionClient } from "../safe-action";
import { createEmployeeSchema } from "../schema";
import type { ActionResponse } from "../types";

const DEFAULT_TASKS = [
{
code: "POLICY-ACCEPT",
name: "Policy Acceptance",
description: "Review and accept company policies",
},
{
code: "INSTALL-AGENT",
name: "Install Monitoring Agent",
description:
"Install and configure the security monitoring agent on your device",
},
{
code: "DEVICE-SECURITY",
name: "Device Security",
description: "Complete device security checklist and configuration",
},
] as const;
import { completeEmployeeCreation } from "@/lib/db/employee";

export const createEmployeeAction = authActionClient
.schema(createEmployeeSchema)
Expand All @@ -46,110 +27,14 @@ export const createEmployeeAction = authActionClient
}

try {
// First check if an employee exists (active or inactive)
const existingEmployee = await db.employee.findUnique({
where: {
email_organizationId: {
email,
organizationId: user.organizationId,
},
},
const employee = await completeEmployeeCreation({
name,
email,
department,
organizationId: user.organizationId,
externalEmployeeId,
});

let employee: Employee;

if (existingEmployee) {
if (existingEmployee.isActive) {
return {
success: false,
error:
"An employee with this email already exists in your organization",
};
}

// Reactivate the existing employee
employee = await db.employee.update({
where: { id: existingEmployee.id },
data: {
name,
department,
isActive: true,
externalEmployeeId,
organizationId: user.organizationId,
updatedAt: new Date(),
},
});
} else {
employee = await db.employee.create({
data: {
name,
email,
department,
organizationId: user.organizationId,
isActive: true,
externalEmployeeId,
},
});
}

// Update or create portalUser
const portalUser = await db.portalUser.upsert({
where: { email },
create: {
id: employee.id,
name,
email,
organizationId: user.organizationId,
emailVerified: false,
createdAt: new Date(),
updatedAt: new Date(),
employees: {
connect: {
id: employee.id,
},
},
},
update: {
updatedAt: new Date(),
name,
email,
organizationId: user.organizationId,
employees: {
connect: {
id: employee.id,
},
},
},
});

// Create or get the required task definitions first and store their IDs
const requiredTasks = await Promise.all(
DEFAULT_TASKS.map(async (task) => {
return db.employeeRequiredTask.upsert({
where: { code: task.code },
create: {
code: task.code,
name: task.name,
description: task.description,
},
update: {},
});
}),
);

// Now create the employee tasks using the actual task IDs
await Promise.all(
requiredTasks.map(async (task) => {
return db.employeeTask.create({
data: {
employeeId: employee.id,
requiredTaskId: task.id,
status: "assigned",
},
});
}),
);

return {
success: true,
data: employee,
Expand All @@ -170,7 +55,8 @@ export const createEmployeeAction = authActionClient

return {
success: false,
error: "Failed to create employee",
error:
error instanceof Error ? error.message : "Failed to create employee",
};
}
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { useComplianceScores } from "@/hooks/use-compliance-scores";
import { useI18n } from "@/locales/client";
import type {
Framework,
Expand All @@ -8,9 +9,8 @@ import type {
} from "@bubba/db";
import { Button } from "@bubba/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@bubba/ui/card";
import { FileStack, FileText, CheckCircle, Cloud } from "lucide-react";
import { FileStack } from "lucide-react";
import Link from "next/link";
import { useComplianceScores } from "@/hooks/use-compliance-scores";

interface Props {
frameworks: (OrganizationFramework & {
Expand All @@ -32,12 +32,10 @@ export function FrameworkProgress({ frameworks }: Props) {
const CircleProgress = ({
percentage,
label,
icon,
href,
}: {
percentage: number;
label: string;
icon: React.ReactNode;
href: string;
}) => (
<Link
Expand Down Expand Up @@ -73,7 +71,6 @@ export function FrameworkProgress({ frameworks }: Props) {
</div>
</div>
<div className="mt-2 flex items-center gap-1.5">
{icon}
<span className="text-sm font-medium">{label}</span>
</div>
</Link>
Expand Down Expand Up @@ -155,19 +152,16 @@ export function FrameworkProgress({ frameworks }: Props) {
<CircleProgress
percentage={policiesCompliance}
label="Policies"
icon={<FileText className="h-4 w-4" />}
href="/policies/all"
/>
<CircleProgress
percentage={evidenceTasksCompliance}
label="Evidence Tasks"
icon={<CheckCircle className="h-4 w-4" />}
href="/evidence/list"
/>
<CircleProgress
percentage={cloudTestsCompliance}
label="Cloud Tests"
icon={<Cloud className="h-4 w-4" />}
href="/tests"
/>
</div>
Expand Down
Loading