This commit is contained in:
2026-01-04 20:58:47 +09:00
parent a87c11597a
commit 0660ed3973
37 changed files with 1723 additions and 885 deletions

View File

@@ -27,6 +27,7 @@ export default defineEventHandler(async (event) => {
projectId: project.project_id,
projectCode: project.project_code,
projectName: project.project_name,
projectType: project.project_type || 'SI',
clientName: project.client_name,
projectDescription: project.project_description,
startDate: project.start_date,

View File

@@ -1,5 +1,7 @@
import { execute, queryOne, insertReturning } from '../../../utils/db'
import { formatDate } from '../../../utils/week-calc'
import { getClientIp } from '../../../utils/ip'
import { getCurrentUserEmail } from '../../../utils/user'
interface AssignManagerBody {
employeeId: number
@@ -15,6 +17,8 @@ interface AssignManagerBody {
export default defineEventHandler(async (event) => {
const projectId = getRouterParam(event, 'id')
const body = await readBody<AssignManagerBody>(event)
const clientIp = getClientIp(event)
const userEmail = await getCurrentUserEmail(event)
if (!body.employeeId || !body.roleType) {
throw createError({ statusCode: 400, message: '담당자와 역할을 선택해주세요.' })
@@ -31,17 +35,21 @@ export default defineEventHandler(async (event) => {
await execute(`
UPDATE wr_project_manager_history SET
end_date = $1,
change_reason = COALESCE(change_reason || ' / ', '') || '신규 담당자 지정으로 종료'
WHERE project_id = $2 AND role_type = $3 AND end_date IS NULL
`, [startDate, projectId, body.roleType])
change_reason = COALESCE(change_reason || ' / ', '') || '신규 담당자 지정으로 종료',
updated_at = NOW(),
updated_ip = $2,
updated_email = $3
WHERE project_id = $4 AND role_type = $5 AND end_date IS NULL
`, [startDate, clientIp, userEmail, projectId, body.roleType])
// 신규 담당자 등록
const history = await insertReturning(`
INSERT INTO wr_project_manager_history (
project_id, employee_id, role_type, start_date, change_reason
) VALUES ($1, $2, $3, $4, $5)
project_id, employee_id, role_type, start_date, change_reason,
created_ip, created_email, updated_ip, updated_email
) VALUES ($1, $2, $3, $4, $5, $6, $7, $6, $7)
RETURNING *
`, [projectId, body.employeeId, body.roleType, startDate, body.changeReason || null])
`, [projectId, body.employeeId, body.roleType, startDate, body.changeReason || null, clientIp, userEmail])
return {
success: true,

View File

@@ -1,8 +1,10 @@
import { execute, queryOne } from '../../../utils/db'
import { getClientIp } from '../../../utils/ip'
import { getCurrentUserEmail } from '../../../utils/user'
interface UpdateProjectBody {
projectCode?: string
projectName?: string
projectType?: string
clientName?: string
projectDescription?: string
startDate?: string
@@ -18,6 +20,8 @@ interface UpdateProjectBody {
export default defineEventHandler(async (event) => {
const projectId = getRouterParam(event, 'id')
const body = await readBody<UpdateProjectBody>(event)
const clientIp = getClientIp(event)
const userEmail = await getCurrentUserEmail(event)
const existing = await queryOne<any>(`
SELECT * FROM wr_project_info WHERE project_id = $1
@@ -27,27 +31,36 @@ export default defineEventHandler(async (event) => {
throw createError({ statusCode: 404, message: '프로젝트를 찾을 수 없습니다.' })
}
// 프로젝트 유형 검증
if (body.projectType && !['SI', 'SM'].includes(body.projectType)) {
throw createError({ statusCode: 400, message: '프로젝트 유형은 SI 또는 SM이어야 합니다.' })
}
await execute(`
UPDATE wr_project_info SET
project_code = $1,
project_name = $2,
project_name = $1,
project_type = $2,
client_name = $3,
project_description = $4,
start_date = $5,
end_date = $6,
contract_amount = $7,
project_status = $8,
updated_at = NOW()
WHERE project_id = $9
updated_at = NOW(),
updated_ip = $9,
updated_email = $10
WHERE project_id = $11
`, [
body.projectCode ?? existing.project_code,
body.projectName ?? existing.project_name,
body.projectType ?? existing.project_type ?? 'SI',
body.clientName ?? existing.client_name,
body.projectDescription ?? existing.project_description,
body.startDate ?? existing.start_date,
body.endDate ?? existing.end_date,
body.contractAmount ?? existing.contract_amount,
body.projectStatus ?? existing.project_status,
clientIp,
userEmail,
projectId
])

View File

@@ -1,8 +1,10 @@
import { insertReturning } from '../../utils/db'
import { query, insertReturning } from '../../utils/db'
import { getClientIp } from '../../utils/ip'
import { getCurrentUserEmail } from '../../utils/user'
interface CreateProjectBody {
projectCode?: string
projectName: string
projectType?: string // SI / SM
clientName?: string
projectDescription?: string
startDate?: string
@@ -10,38 +12,80 @@ interface CreateProjectBody {
contractAmount?: number
}
/**
* 프로젝트 코드 자동 생성 (년도-일련번호)
*/
async function generateProjectCode(): Promise<string> {
const year = new Date().getFullYear()
const prefix = `${year}-`
// 해당 연도의 마지막 코드 조회
const result = await query<{ project_code: string }>(`
SELECT project_code FROM wr_project_info
WHERE project_code LIKE $1
ORDER BY project_code DESC
LIMIT 1
`, [`${prefix}%`])
let nextNum = 1
if (result.length > 0 && result[0].project_code) {
const lastCode = result[0].project_code
const lastNum = parseInt(lastCode.split('-')[1]) || 0
nextNum = lastNum + 1
}
return `${prefix}${String(nextNum).padStart(3, '0')}`
}
/**
* 프로젝트 등록
* POST /api/project/create
*/
export default defineEventHandler(async (event) => {
const body = await readBody<CreateProjectBody>(event)
const clientIp = getClientIp(event)
const userEmail = await getCurrentUserEmail(event)
if (!body.projectName) {
throw createError({ statusCode: 400, message: '프로젝트명을 입력해주세요.' })
}
// 프로젝트 유형 검증
const projectType = body.projectType || 'SI'
if (!['SI', 'SM'].includes(projectType)) {
throw createError({ statusCode: 400, message: '프로젝트 유형은 SI 또는 SM이어야 합니다.' })
}
// 프로젝트 코드 자동 생성
const projectCode = await generateProjectCode()
const project = await insertReturning(`
INSERT INTO wr_project_info (
project_code, project_name, client_name, project_description,
start_date, end_date, contract_amount
) VALUES ($1, $2, $3, $4, $5, $6, $7)
project_code, project_name, project_type, client_name, project_description,
start_date, end_date, contract_amount,
created_ip, created_email, updated_ip, updated_email
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $9, $10)
RETURNING *
`, [
body.projectCode || null,
projectCode,
body.projectName,
projectType,
body.clientName || null,
body.projectDescription || null,
body.startDate || null,
body.endDate || null,
body.contractAmount || null
body.contractAmount || null,
clientIp,
userEmail
])
return {
success: true,
project: {
projectId: project.project_id,
projectName: project.project_name
projectCode: project.project_code,
projectName: project.project_name,
projectType: project.project_type
}
}
})

View File

@@ -38,6 +38,7 @@ export default defineEventHandler(async (event) => {
projectId: p.project_id,
projectCode: p.project_code,
projectName: p.project_name,
projectType: p.project_type || 'SI',
clientName: p.client_name,
projectDescription: p.project_description,
startDate: p.start_date,