Initial commit - EENE Dashboard
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
147
backend/prisma/migrations/20260528092950_init/migration.sql
Normal file
147
backend/prisma/migrations/20260528092950_init/migration.sql
Normal file
@@ -0,0 +1,147 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Role" AS ENUM ('ADMIN', 'MANAGER', 'MEMBER');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "TaskStatus" AS ENUM ('TODO', 'IN_PROGRESS', 'REVIEW', 'DONE', 'CANCELLED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Priority" AS ENUM ('LOW', 'MEDIUM', 'HIGH', 'URGENT');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "users" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"role" "Role" NOT NULL DEFAULT 'MEMBER',
|
||||
"department" TEXT,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "tasks" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"status" "TaskStatus" NOT NULL DEFAULT 'TODO',
|
||||
"priority" "Priority" NOT NULL DEFAULT 'MEDIUM',
|
||||
"quarter" TEXT NOT NULL,
|
||||
"category" TEXT,
|
||||
"dueDate" TIMESTAMP(3),
|
||||
"creatorId" TEXT NOT NULL,
|
||||
"assigneeId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "tasks_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "task_details" (
|
||||
"id" TEXT NOT NULL,
|
||||
"taskId" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"updatedBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "task_details_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "kpi_metrics" (
|
||||
"id" TEXT NOT NULL,
|
||||
"taskId" TEXT NOT NULL,
|
||||
"quarter" TEXT NOT NULL,
|
||||
"target" DOUBLE PRECISION NOT NULL,
|
||||
"actual" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||
"unit" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "kpi_metrics_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "files" (
|
||||
"id" TEXT NOT NULL,
|
||||
"taskId" TEXT NOT NULL,
|
||||
"filename" TEXT NOT NULL,
|
||||
"originalName" TEXT NOT NULL,
|
||||
"mimetype" TEXT NOT NULL,
|
||||
"size" INTEGER NOT NULL,
|
||||
"path" TEXT NOT NULL,
|
||||
"uploadedBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "files_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "audit_logs" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"action" TEXT NOT NULL,
|
||||
"entity" TEXT NOT NULL,
|
||||
"entityId" TEXT,
|
||||
"details" JSONB,
|
||||
"ipAddress" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "audit_logs_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "tasks_quarter_idx" ON "tasks"("quarter");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "tasks_status_idx" ON "tasks"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "tasks_assigneeId_idx" ON "tasks"("assigneeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "task_details_taskId_idx" ON "task_details"("taskId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "kpi_metrics_quarter_idx" ON "kpi_metrics"("quarter");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "files_taskId_idx" ON "files"("taskId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "audit_logs_userId_idx" ON "audit_logs"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "audit_logs_createdAt_idx" ON "audit_logs"("createdAt");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_assigneeId_fkey" FOREIGN KEY ("assigneeId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "task_details" ADD CONSTRAINT "task_details_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "tasks"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "task_details" ADD CONSTRAINT "task_details_updatedBy_fkey" FOREIGN KEY ("updatedBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "kpi_metrics" ADD CONSTRAINT "kpi_metrics_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "tasks"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "files" ADD CONSTRAINT "files_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "tasks"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "files" ADD CONSTRAINT "files_uploadedBy_fkey" FOREIGN KEY ("uploadedBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,9 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "tasks" ADD COLUMN "issueNote" TEXT,
|
||||
ADD COLUMN "progress" INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN "section" TEXT,
|
||||
ADD COLUMN "tag" TEXT,
|
||||
ADD COLUMN "taskType" TEXT;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "tasks_section_idx" ON "tasks"("section");
|
||||
3
backend/prisma/migrations/migration_lock.toml
Normal file
3
backend/prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
193
backend/prisma/schema.prisma
Normal file
193
backend/prisma/schema.prisma
Normal file
@@ -0,0 +1,193 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ─── 사용자 ──────────────────────────────────────────────────
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
password String
|
||||
name String
|
||||
role Role @default(MEMBER)
|
||||
department String?
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
createdTasks Task[] @relation("CreatedTasks")
|
||||
assignedTasks Task[] @relation("AssignedTasks")
|
||||
taskDetails TaskDetail[]
|
||||
uploadedFiles File[]
|
||||
auditLogs AuditLog[]
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
enum Role {
|
||||
ADMIN
|
||||
MANAGER
|
||||
MEMBER
|
||||
}
|
||||
|
||||
// ─── 업무 ────────────────────────────────────────────────────
|
||||
|
||||
model Task {
|
||||
id String @id @default(cuid())
|
||||
title String
|
||||
description String?
|
||||
status TaskStatus @default(TODO)
|
||||
priority Priority @default(MEDIUM)
|
||||
quarter String // 예: "2026-Q2"
|
||||
category String?
|
||||
section String? // HR | 운영관리
|
||||
tag String? // Growth | Policy | Performance | Culture | Asset | Space | Safety | Environment
|
||||
taskType String? // 상시업무 | 프로젝트
|
||||
progress Int @default(0)
|
||||
issueNote String?
|
||||
startDate DateTime?
|
||||
dueDate DateTime?
|
||||
creatorId String
|
||||
assigneeId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
creator User @relation("CreatedTasks", fields: [creatorId], references: [id])
|
||||
assignee User? @relation("AssignedTasks", fields: [assigneeId], references: [id])
|
||||
details TaskDetail[]
|
||||
kpiMetrics KpiMetric[]
|
||||
files File[]
|
||||
milestones Milestone[]
|
||||
|
||||
@@index([quarter])
|
||||
@@index([status])
|
||||
@@index([assigneeId])
|
||||
@@index([section])
|
||||
@@map("tasks")
|
||||
}
|
||||
|
||||
enum TaskStatus {
|
||||
TODO
|
||||
IN_PROGRESS
|
||||
REVIEW
|
||||
DONE
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum Priority {
|
||||
LOW
|
||||
MEDIUM
|
||||
HIGH
|
||||
URGENT
|
||||
}
|
||||
|
||||
// ─── 업무 상세 / 진행 기록 ────────────────────────────────────
|
||||
|
||||
model TaskDetail {
|
||||
id String @id @default(cuid())
|
||||
taskId String
|
||||
content String
|
||||
updatedBy String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
||||
author User @relation(fields: [updatedBy], references: [id])
|
||||
|
||||
@@index([taskId])
|
||||
@@map("task_details")
|
||||
}
|
||||
|
||||
// ─── KPI 지표 ────────────────────────────────────────────────
|
||||
|
||||
model KpiMetric {
|
||||
id String @id @default(cuid())
|
||||
taskId String
|
||||
quarter String
|
||||
target Float
|
||||
actual Float @default(0)
|
||||
unit String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([quarter])
|
||||
@@map("kpi_metrics")
|
||||
}
|
||||
|
||||
// ─── 파일 ────────────────────────────────────────────────────
|
||||
|
||||
model File {
|
||||
id String @id @default(cuid())
|
||||
taskId String
|
||||
filename String // 저장된 파일명 (UUID)
|
||||
originalName String // 원본 파일명
|
||||
mimetype String
|
||||
size Int
|
||||
path String
|
||||
uploadedBy String
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
||||
uploader User @relation(fields: [uploadedBy], references: [id])
|
||||
|
||||
@@index([taskId])
|
||||
@@map("files")
|
||||
}
|
||||
|
||||
// ─── 마일스톤 (프로세스 단계) ─────────────────────────────────
|
||||
|
||||
model Milestone {
|
||||
id String @id @default(cuid())
|
||||
taskId String
|
||||
title String
|
||||
description String?
|
||||
dueDate DateTime?
|
||||
completedAt DateTime?
|
||||
order Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([taskId])
|
||||
@@map("milestones")
|
||||
}
|
||||
|
||||
// ─── 컬럼 설정 ───────────────────────────────────────────────
|
||||
|
||||
model ColumnConfig {
|
||||
key String @id // "HR" | "운영관리"
|
||||
title String
|
||||
titleEn String?
|
||||
subtitle String?
|
||||
cardOrder String? // JSON 배열: task id 순서
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@map("column_configs")
|
||||
}
|
||||
|
||||
// ─── 감사 로그 ───────────────────────────────────────────────
|
||||
|
||||
model AuditLog {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
action String // CREATE, UPDATE, DELETE, LOGIN 등
|
||||
entity String // Task, User, File 등
|
||||
entityId String?
|
||||
details Json?
|
||||
ipAddress String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
@@index([userId])
|
||||
@@index([createdAt])
|
||||
@@map("audit_logs")
|
||||
}
|
||||
145
backend/prisma/seed.ts
Normal file
145
backend/prisma/seed.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import 'dotenv/config';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('🌱 Seeding database...');
|
||||
|
||||
// ─── 사용자 ─────────────────────────────────────────────
|
||||
const adminPw = await bcrypt.hash('admin1234!', 12);
|
||||
const memberPw = await bcrypt.hash('member1234!', 12);
|
||||
|
||||
const admin = await prisma.user.upsert({
|
||||
where: { email: 'admin@eene.com' },
|
||||
update: {},
|
||||
create: { email: 'admin@eene.com', password: adminPw, name: '관리자', role: 'ADMIN', department: 'EENE' },
|
||||
});
|
||||
|
||||
const member = await prisma.user.upsert({
|
||||
where: { email: 'member@eene.com' },
|
||||
update: {},
|
||||
create: { email: 'member@eene.com', password: memberPw, name: '홍길동', role: 'MEMBER', department: 'EENE' },
|
||||
});
|
||||
|
||||
console.log(`✅ Users ready`);
|
||||
|
||||
// ─── 기존 업무 삭제 후 재생성 ─────────────────────────
|
||||
await prisma.kpiMetric.deleteMany({});
|
||||
await prisma.taskDetail.deleteMany({});
|
||||
await prisma.task.deleteMany({});
|
||||
|
||||
// ─── HR 부문 업무 ──────────────────────────────────────
|
||||
const hrTasks = [
|
||||
{
|
||||
title: '사내 핵심역량 교육체계 수립 (HRD)',
|
||||
description: '직급별/직무별 필수 역량 가이드라인 도출\n사내 강사 제도 양성 및 콘텐츠 기획 단계',
|
||||
status: 'IN_PROGRESS' as const,
|
||||
priority: 'HIGH' as const,
|
||||
section: 'HR',
|
||||
tag: 'Growth',
|
||||
taskType: '프로젝트',
|
||||
progress: 20,
|
||||
issueNote: '[5.28] 사내 강사 풀 확보 및 보상안 검토',
|
||||
},
|
||||
{
|
||||
title: '그룹 표준 취업규칙 개정안 (HRM)',
|
||||
description: '유연근무제 및 근태 관리 프로세스 고도화\n노사협의회 안건 조율 및 근로조건 개선안 반영',
|
||||
status: 'IN_PROGRESS' as const,
|
||||
priority: 'HIGH' as const,
|
||||
section: 'HR',
|
||||
tag: 'Policy',
|
||||
taskType: '프로젝트',
|
||||
progress: 40,
|
||||
issueNote: '[5.28] 가족사 간 피드백 이견 조율 진행',
|
||||
},
|
||||
{
|
||||
title: '상반기 평가 지표(KPI) 보완',
|
||||
description: '1분기 피드백 기반 부서별 KPI 정렬\n상반기 업적 평가 시뮬레이션 기획',
|
||||
status: 'IN_PROGRESS' as const,
|
||||
priority: 'MEDIUM' as const,
|
||||
section: 'HR',
|
||||
tag: 'Performance',
|
||||
taskType: '상시업무',
|
||||
progress: 70,
|
||||
issueNote: null,
|
||||
},
|
||||
{
|
||||
title: '가족사 시너지 조직문화 캠페인',
|
||||
description: '임직원 만족도 조사(Engagement Survey) 설계',
|
||||
status: 'TODO' as const,
|
||||
priority: 'LOW' as const,
|
||||
section: 'HR',
|
||||
tag: 'Culture',
|
||||
taskType: '프로젝트',
|
||||
progress: 0,
|
||||
issueNote: null,
|
||||
},
|
||||
];
|
||||
|
||||
// ─── 운영관리 부문 업무 ────────────────────────────────
|
||||
const opsTasks = [
|
||||
{
|
||||
title: '全사 IT자산 수명주기 표준화 구축',
|
||||
description: '자산 전수조사 데이터 기반 불용 기준 정립\n라이선스 최적화를 통한 비용 절감안 도출',
|
||||
status: 'IN_PROGRESS' as const,
|
||||
priority: 'HIGH' as const,
|
||||
section: '운영관리',
|
||||
tag: 'Asset',
|
||||
taskType: '프로젝트',
|
||||
progress: 60,
|
||||
issueNote: null,
|
||||
},
|
||||
{
|
||||
title: '기술센터 2F 세미나룸 브랜딩 기획',
|
||||
description: '다목적 교육 공간 활용을 위한 운영 매뉴얼 수립\n공간 아이덴티티 반영 사인물(Signage) 기획',
|
||||
status: 'REVIEW' as const,
|
||||
priority: 'MEDIUM' as const,
|
||||
section: '운영관리',
|
||||
tag: 'Space',
|
||||
taskType: '프로젝트',
|
||||
progress: 80,
|
||||
issueNote: null,
|
||||
},
|
||||
{
|
||||
title: '중대재해법 대응 안전보건 매뉴얼 정비',
|
||||
description: '현장 점검 기반 소방 및 MSDS 관리 체계 보완\n사내 안전사고 예방 가이드라인 표준화 기획',
|
||||
status: 'IN_PROGRESS' as const,
|
||||
priority: 'HIGH' as const,
|
||||
section: '운영관리',
|
||||
tag: 'Safety',
|
||||
taskType: '상시업무',
|
||||
progress: 30,
|
||||
issueNote: null,
|
||||
},
|
||||
{
|
||||
title: '공용 공간 인프라 개선',
|
||||
description: '기술센터 로비 및 라운지 환경 정비 기획',
|
||||
status: 'TODO' as const,
|
||||
priority: 'LOW' as const,
|
||||
section: '운영관리',
|
||||
tag: 'Environment',
|
||||
taskType: '프로젝트',
|
||||
progress: 0,
|
||||
issueNote: null,
|
||||
},
|
||||
];
|
||||
|
||||
for (const t of [...hrTasks, ...opsTasks]) {
|
||||
await prisma.task.create({
|
||||
data: {
|
||||
...t,
|
||||
quarter: '2026-Q2',
|
||||
category: t.section,
|
||||
creatorId: admin.id,
|
||||
assigneeId: member.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`✅ Tasks created: ${hrTasks.length + opsTasks.length}개`);
|
||||
console.log('🎉 Seeding complete!');
|
||||
}
|
||||
|
||||
main().catch(console.error).finally(() => prisma.$disconnect());
|
||||
Reference in New Issue
Block a user