947 lines
43 KiB
TypeScript
947 lines
43 KiB
TypeScript
|
|
import { Response } from "express";
|
||
|
|
import { AuthenticatedRequest } from "../types/auth";
|
||
|
|
import { query, getPool } from "../database/db";
|
||
|
|
import { logger } from "../utils/logger";
|
||
|
|
|
||
|
|
// 회사코드 필터 조건 생성 헬퍼
|
||
|
|
function companyFilter(companyCode: string, paramIndex: number, alias?: string): { condition: string; param: string; nextIndex: number } {
|
||
|
|
const col = alias ? `${alias}.company_code` : "company_code";
|
||
|
|
if (companyCode === "*") {
|
||
|
|
return { condition: "", param: "", nextIndex: paramIndex };
|
||
|
|
}
|
||
|
|
return { condition: `${col} = $${paramIndex}`, param: companyCode, nextIndex: paramIndex + 1 };
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================
|
||
|
|
// 설계의뢰/설변요청 (DR/ECR) CRUD
|
||
|
|
// ============================================
|
||
|
|
|
||
|
|
export async function getDesignRequestList(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { source_type, status, priority, search } = req.query;
|
||
|
|
|
||
|
|
const conditions: string[] = [];
|
||
|
|
const params: any[] = [];
|
||
|
|
let pi = 1;
|
||
|
|
|
||
|
|
if (companyCode !== "*") {
|
||
|
|
conditions.push(`r.company_code = $${pi}`);
|
||
|
|
params.push(companyCode);
|
||
|
|
pi++;
|
||
|
|
}
|
||
|
|
if (source_type) { conditions.push(`r.source_type = $${pi}`); params.push(source_type); pi++; }
|
||
|
|
if (status) { conditions.push(`r.status = $${pi}`); params.push(status); pi++; }
|
||
|
|
if (priority) { conditions.push(`r.priority = $${pi}`); params.push(priority); pi++; }
|
||
|
|
if (search) {
|
||
|
|
conditions.push(`(r.target_name ILIKE $${pi} OR r.request_no ILIKE $${pi} OR r.requester ILIKE $${pi})`);
|
||
|
|
params.push(`%${search}%`);
|
||
|
|
pi++;
|
||
|
|
}
|
||
|
|
|
||
|
|
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
||
|
|
const sql = `
|
||
|
|
SELECT r.*,
|
||
|
|
COALESCE(json_agg(json_build_object('id', h.id, 'step', h.step, 'history_date', h.history_date, 'user_name', h.user_name, 'description', h.description)) FILTER (WHERE h.id IS NOT NULL), '[]') AS history,
|
||
|
|
COALESCE((SELECT json_agg(i.impact_type) FROM dsn_request_impact i WHERE i.request_id = r.id), '[]') AS impact
|
||
|
|
FROM dsn_design_request r
|
||
|
|
LEFT JOIN dsn_request_history h ON h.request_id = r.id
|
||
|
|
${where}
|
||
|
|
GROUP BY r.id
|
||
|
|
ORDER BY r.created_date DESC
|
||
|
|
`;
|
||
|
|
const result = await query(sql, params);
|
||
|
|
res.json({ success: true, data: result });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("설계의뢰 목록 조회 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function getDesignRequestDetail(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { id } = req.params;
|
||
|
|
|
||
|
|
const conditions = [`r.id = $1`];
|
||
|
|
const params: any[] = [id];
|
||
|
|
if (companyCode !== "*") { conditions.push(`r.company_code = $2`); params.push(companyCode); }
|
||
|
|
|
||
|
|
const sql = `
|
||
|
|
SELECT r.*,
|
||
|
|
COALESCE((SELECT json_agg(json_build_object('id', h.id, 'step', h.step, 'history_date', h.history_date, 'user_name', h.user_name, 'description', h.description) ORDER BY h.created_date) FROM dsn_request_history h WHERE h.request_id = r.id), '[]') AS history,
|
||
|
|
COALESCE((SELECT json_agg(i.impact_type) FROM dsn_request_impact i WHERE i.request_id = r.id), '[]') AS impact
|
||
|
|
FROM dsn_design_request r
|
||
|
|
WHERE ${conditions.join(" AND ")}
|
||
|
|
`;
|
||
|
|
const result = await query(sql, params);
|
||
|
|
if (!result.length) { res.status(404).json({ success: false, message: "의뢰를 찾을 수 없습니다." }); return; }
|
||
|
|
res.json({ success: true, data: result[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("설계의뢰 상세 조회 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function createDesignRequest(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
const pool = getPool();
|
||
|
|
const client = await pool.connect();
|
||
|
|
try {
|
||
|
|
await client.query("BEGIN");
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const userId = req.user!.userId;
|
||
|
|
const {
|
||
|
|
request_no, source_type, request_date, due_date, priority, status,
|
||
|
|
target_name, customer, req_dept, requester, designer, order_no,
|
||
|
|
design_type, spec, change_type, drawing_no, urgency, reason,
|
||
|
|
content, apply_timing, review_memo, project_id, ecn_no,
|
||
|
|
impact, history,
|
||
|
|
} = req.body;
|
||
|
|
|
||
|
|
const sql = `
|
||
|
|
INSERT INTO dsn_design_request (
|
||
|
|
request_no, source_type, request_date, due_date, priority, status,
|
||
|
|
target_name, customer, req_dept, requester, designer, order_no,
|
||
|
|
design_type, spec, change_type, drawing_no, urgency, reason,
|
||
|
|
content, apply_timing, review_memo, project_id, ecn_no,
|
||
|
|
writer, company_code
|
||
|
|
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25)
|
||
|
|
RETURNING *
|
||
|
|
`;
|
||
|
|
const result = await client.query(sql, [
|
||
|
|
request_no, source_type || "dr", request_date, due_date, priority || "보통", status || "신규접수",
|
||
|
|
target_name, customer, req_dept, requester, designer, order_no,
|
||
|
|
design_type, spec, change_type, drawing_no, urgency || "보통", reason,
|
||
|
|
content, apply_timing, review_memo, project_id, ecn_no,
|
||
|
|
userId, companyCode,
|
||
|
|
]);
|
||
|
|
|
||
|
|
const requestId = result.rows[0].id;
|
||
|
|
|
||
|
|
if (impact?.length) {
|
||
|
|
for (const imp of impact) {
|
||
|
|
await client.query(
|
||
|
|
`INSERT INTO dsn_request_impact (request_id, impact_type, writer, company_code) VALUES ($1,$2,$3,$4)`,
|
||
|
|
[requestId, imp, userId, companyCode]
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (history?.length) {
|
||
|
|
for (const h of history) {
|
||
|
|
await client.query(
|
||
|
|
`INSERT INTO dsn_request_history (request_id, step, history_date, user_name, description, writer, company_code) VALUES ($1,$2,$3,$4,$5,$6,$7)`,
|
||
|
|
[requestId, h.step, h.history_date, h.user_name, h.description, userId, companyCode]
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
await client.query("COMMIT");
|
||
|
|
res.json({ success: true, data: result.rows[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
await client.query("ROLLBACK");
|
||
|
|
logger.error("설계의뢰 생성 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
} finally {
|
||
|
|
client.release();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function updateDesignRequest(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
const pool = getPool();
|
||
|
|
const client = await pool.connect();
|
||
|
|
try {
|
||
|
|
await client.query("BEGIN");
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const userId = req.user!.userId;
|
||
|
|
const { id } = req.params;
|
||
|
|
const {
|
||
|
|
request_no, source_type, request_date, due_date, priority, status, approval_step,
|
||
|
|
target_name, customer, req_dept, requester, designer, order_no,
|
||
|
|
design_type, spec, change_type, drawing_no, urgency, reason,
|
||
|
|
content, apply_timing, review_memo, project_id, ecn_no,
|
||
|
|
impact, history,
|
||
|
|
} = req.body;
|
||
|
|
|
||
|
|
const conditions = [`id = $1`];
|
||
|
|
const params: any[] = [id];
|
||
|
|
let pi = 2;
|
||
|
|
if (companyCode !== "*") { conditions.push(`company_code = $${pi}`); params.push(companyCode); pi++; }
|
||
|
|
|
||
|
|
const setClauses = [];
|
||
|
|
const setParams: any[] = [];
|
||
|
|
const fields: Record<string, any> = {
|
||
|
|
request_no, source_type, request_date, due_date, priority, status, approval_step,
|
||
|
|
target_name, customer, req_dept, requester, designer, order_no,
|
||
|
|
design_type, spec, change_type, drawing_no, urgency, reason,
|
||
|
|
content, apply_timing, review_memo, project_id, ecn_no,
|
||
|
|
};
|
||
|
|
for (const [key, val] of Object.entries(fields)) {
|
||
|
|
if (val !== undefined) {
|
||
|
|
setClauses.push(`${key} = $${pi}`);
|
||
|
|
setParams.push(val);
|
||
|
|
pi++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
setClauses.push(`updated_date = now()`);
|
||
|
|
|
||
|
|
const sql = `UPDATE dsn_design_request SET ${setClauses.join(", ")} WHERE ${conditions.join(" AND ")} RETURNING *`;
|
||
|
|
const result = await client.query(sql, [...params, ...setParams]);
|
||
|
|
if (!result.rowCount) { await client.query("ROLLBACK"); res.status(404).json({ success: false, message: "의뢰를 찾을 수 없습니다." }); return; }
|
||
|
|
|
||
|
|
if (impact !== undefined) {
|
||
|
|
await client.query(`DELETE FROM dsn_request_impact WHERE request_id = $1`, [id]);
|
||
|
|
for (const imp of impact) {
|
||
|
|
await client.query(
|
||
|
|
`INSERT INTO dsn_request_impact (request_id, impact_type, writer, company_code) VALUES ($1,$2,$3,$4)`,
|
||
|
|
[id, imp, userId, companyCode]
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (history !== undefined) {
|
||
|
|
await client.query(`DELETE FROM dsn_request_history WHERE request_id = $1`, [id]);
|
||
|
|
for (const h of history) {
|
||
|
|
await client.query(
|
||
|
|
`INSERT INTO dsn_request_history (request_id, step, history_date, user_name, description, writer, company_code) VALUES ($1,$2,$3,$4,$5,$6,$7)`,
|
||
|
|
[id, h.step, h.history_date, h.user_name, h.description, userId, companyCode]
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
await client.query("COMMIT");
|
||
|
|
res.json({ success: true, data: result.rows[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
await client.query("ROLLBACK");
|
||
|
|
logger.error("설계의뢰 수정 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
} finally {
|
||
|
|
client.release();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function deleteDesignRequest(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { id } = req.params;
|
||
|
|
|
||
|
|
const conditions = [`id = $1`];
|
||
|
|
const params: any[] = [id];
|
||
|
|
if (companyCode !== "*") { conditions.push(`company_code = $2`); params.push(companyCode); }
|
||
|
|
|
||
|
|
const sql = `DELETE FROM dsn_design_request WHERE ${conditions.join(" AND ")} RETURNING id`;
|
||
|
|
const result = await query(sql, params);
|
||
|
|
if (!result.length) { res.status(404).json({ success: false, message: "의뢰를 찾을 수 없습니다." }); return; }
|
||
|
|
res.json({ success: true });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("설계의뢰 삭제 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 이력 추가 (단건)
|
||
|
|
export async function addRequestHistory(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const userId = req.user!.userId;
|
||
|
|
const { id } = req.params;
|
||
|
|
const { step, history_date, user_name, description } = req.body;
|
||
|
|
|
||
|
|
const sql = `INSERT INTO dsn_request_history (request_id, step, history_date, user_name, description, writer, company_code) VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING *`;
|
||
|
|
const result = await query(sql, [id, step, history_date, user_name, description, userId, companyCode]);
|
||
|
|
res.json({ success: true, data: result[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("의뢰 이력 추가 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================
|
||
|
|
// 설계 프로젝트 CRUD
|
||
|
|
// ============================================
|
||
|
|
|
||
|
|
export async function getProjectList(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { status, search } = req.query;
|
||
|
|
|
||
|
|
const conditions: string[] = [];
|
||
|
|
const params: any[] = [];
|
||
|
|
let pi = 1;
|
||
|
|
|
||
|
|
if (companyCode !== "*") { conditions.push(`p.company_code = $${pi}`); params.push(companyCode); pi++; }
|
||
|
|
if (status) { conditions.push(`p.status = $${pi}`); params.push(status); pi++; }
|
||
|
|
if (search) {
|
||
|
|
conditions.push(`(p.name ILIKE $${pi} OR p.project_no ILIKE $${pi} OR p.customer ILIKE $${pi})`);
|
||
|
|
params.push(`%${search}%`);
|
||
|
|
pi++;
|
||
|
|
}
|
||
|
|
|
||
|
|
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
||
|
|
const sql = `
|
||
|
|
SELECT p.*,
|
||
|
|
COALESCE(
|
||
|
|
(SELECT json_agg(json_build_object(
|
||
|
|
'id', t.id, 'name', t.name, 'category', t.category, 'assignee', t.assignee,
|
||
|
|
'start_date', t.start_date, 'end_date', t.end_date, 'status', t.status,
|
||
|
|
'progress', t.progress, 'priority', t.priority, 'remark', t.remark, 'sort_order', t.sort_order
|
||
|
|
) ORDER BY t.sort_order, t.start_date)
|
||
|
|
FROM dsn_project_task t WHERE t.project_id = p.id), '[]'
|
||
|
|
) AS tasks
|
||
|
|
FROM dsn_project p
|
||
|
|
${where}
|
||
|
|
ORDER BY p.created_date DESC
|
||
|
|
`;
|
||
|
|
const result = await query(sql, params);
|
||
|
|
res.json({ success: true, data: result });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("프로젝트 목록 조회 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function getProjectDetail(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { id } = req.params;
|
||
|
|
|
||
|
|
const conditions = [`p.id = $1`];
|
||
|
|
const params: any[] = [id];
|
||
|
|
if (companyCode !== "*") { conditions.push(`p.company_code = $2`); params.push(companyCode); }
|
||
|
|
|
||
|
|
const sql = `
|
||
|
|
SELECT p.*,
|
||
|
|
COALESCE(
|
||
|
|
(SELECT json_agg(json_build_object(
|
||
|
|
'id', t.id, 'name', t.name, 'category', t.category, 'assignee', t.assignee,
|
||
|
|
'start_date', t.start_date, 'end_date', t.end_date, 'status', t.status,
|
||
|
|
'progress', t.progress, 'priority', t.priority, 'remark', t.remark, 'sort_order', t.sort_order
|
||
|
|
) ORDER BY t.sort_order, t.start_date)
|
||
|
|
FROM dsn_project_task t WHERE t.project_id = p.id), '[]'
|
||
|
|
) AS tasks
|
||
|
|
FROM dsn_project p
|
||
|
|
WHERE ${conditions.join(" AND ")}
|
||
|
|
`;
|
||
|
|
const result = await query(sql, params);
|
||
|
|
if (!result.length) { res.status(404).json({ success: false, message: "프로젝트를 찾을 수 없습니다." }); return; }
|
||
|
|
res.json({ success: true, data: result[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("프로젝트 상세 조회 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function createProject(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
const pool = getPool();
|
||
|
|
const client = await pool.connect();
|
||
|
|
try {
|
||
|
|
await client.query("BEGIN");
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const userId = req.user!.userId;
|
||
|
|
const { project_no, name, status: pStatus, pm, customer, start_date, end_date, source_no, description, progress, parent_id, relation_type, tasks } = req.body;
|
||
|
|
|
||
|
|
const result = await client.query(
|
||
|
|
`INSERT INTO dsn_project (project_no, name, status, pm, customer, start_date, end_date, source_no, description, progress, parent_id, relation_type, writer, company_code)
|
||
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) RETURNING *`,
|
||
|
|
[project_no, name, pStatus || "계획", pm, customer, start_date, end_date, source_no, description, progress || "0", parent_id, relation_type, userId, companyCode]
|
||
|
|
);
|
||
|
|
|
||
|
|
const projectId = result.rows[0].id;
|
||
|
|
if (tasks?.length) {
|
||
|
|
for (let i = 0; i < tasks.length; i++) {
|
||
|
|
const t = tasks[i];
|
||
|
|
await client.query(
|
||
|
|
`INSERT INTO dsn_project_task (project_id, name, category, assignee, start_date, end_date, status, progress, priority, remark, sort_order, writer, company_code)
|
||
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)`,
|
||
|
|
[projectId, t.name, t.category, t.assignee, t.start_date, t.end_date, t.status || "대기", t.progress || "0", t.priority || "보통", t.remark, String(i), userId, companyCode]
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
await client.query("COMMIT");
|
||
|
|
res.json({ success: true, data: result.rows[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
await client.query("ROLLBACK");
|
||
|
|
logger.error("프로젝트 생성 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
} finally {
|
||
|
|
client.release();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function updateProject(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { id } = req.params;
|
||
|
|
const { project_no, name, status: pStatus, pm, customer, start_date, end_date, source_no, description, progress, parent_id, relation_type } = req.body;
|
||
|
|
|
||
|
|
const conditions = [`id = $1`];
|
||
|
|
const params: any[] = [id];
|
||
|
|
let pi = 2;
|
||
|
|
if (companyCode !== "*") { conditions.push(`company_code = $${pi}`); params.push(companyCode); pi++; }
|
||
|
|
|
||
|
|
const sets: string[] = [];
|
||
|
|
const fields: Record<string, any> = { project_no, name, status: pStatus, pm, customer, start_date, end_date, source_no, description, progress, parent_id, relation_type };
|
||
|
|
for (const [key, val] of Object.entries(fields)) {
|
||
|
|
if (val !== undefined) { sets.push(`${key} = $${pi}`); params.push(val); pi++; }
|
||
|
|
}
|
||
|
|
sets.push(`updated_date = now()`);
|
||
|
|
|
||
|
|
const result = await query(`UPDATE dsn_project SET ${sets.join(", ")} WHERE ${conditions.join(" AND ")} RETURNING *`, params);
|
||
|
|
if (!result.length) { res.status(404).json({ success: false, message: "프로젝트를 찾을 수 없습니다." }); return; }
|
||
|
|
res.json({ success: true, data: result[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("프로젝트 수정 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function deleteProject(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { id } = req.params;
|
||
|
|
const conditions = [`id = $1`];
|
||
|
|
const params: any[] = [id];
|
||
|
|
if (companyCode !== "*") { conditions.push(`company_code = $2`); params.push(companyCode); }
|
||
|
|
|
||
|
|
const result = await query(`DELETE FROM dsn_project WHERE ${conditions.join(" AND ")} RETURNING id`, params);
|
||
|
|
if (!result.length) { res.status(404).json({ success: false, message: "프로젝트를 찾을 수 없습니다." }); return; }
|
||
|
|
res.json({ success: true });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("프로젝트 삭제 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================
|
||
|
|
// 프로젝트 태스크 CRUD
|
||
|
|
// ============================================
|
||
|
|
|
||
|
|
export async function getTasksByProject(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { projectId } = req.params;
|
||
|
|
|
||
|
|
const conditions = [`t.project_id = $1`];
|
||
|
|
const params: any[] = [projectId];
|
||
|
|
if (companyCode !== "*") { conditions.push(`t.company_code = $2`); params.push(companyCode); }
|
||
|
|
|
||
|
|
const sql = `
|
||
|
|
SELECT t.*,
|
||
|
|
COALESCE((SELECT json_agg(json_build_object('id', w.id, 'start_dt', w.start_dt, 'end_dt', w.end_dt, 'hours', w.hours, 'description', w.description, 'progress_before', w.progress_before, 'progress_after', w.progress_after, 'author', w.author, 'sub_item_id', w.sub_item_id) ORDER BY w.start_dt) FROM dsn_work_log w WHERE w.task_id = t.id), '[]') AS work_logs,
|
||
|
|
COALESCE((SELECT json_agg(json_build_object('id', i.id, 'title', i.title, 'status', i.status, 'priority', i.priority, 'description', i.description, 'registered_by', i.registered_by, 'registered_date', i.registered_date, 'resolved_date', i.resolved_date)) FROM dsn_task_issue i WHERE i.task_id = t.id), '[]') AS issues,
|
||
|
|
COALESCE((SELECT json_agg(json_build_object('id', s.id, 'name', s.name, 'weight', s.weight, 'progress', s.progress, 'status', s.status) ORDER BY s.created_date) FROM dsn_task_sub_item s WHERE s.task_id = t.id), '[]') AS sub_items
|
||
|
|
FROM dsn_project_task t
|
||
|
|
WHERE ${conditions.join(" AND ")}
|
||
|
|
ORDER BY t.sort_order, t.start_date
|
||
|
|
`;
|
||
|
|
const result = await query(sql, params);
|
||
|
|
res.json({ success: true, data: result });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("태스크 목록 조회 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function createTask(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const userId = req.user!.userId;
|
||
|
|
const { projectId } = req.params;
|
||
|
|
const { name, category, assignee, start_date, end_date, status, progress, priority, remark, sort_order } = req.body;
|
||
|
|
|
||
|
|
const result = await query(
|
||
|
|
`INSERT INTO dsn_project_task (project_id, name, category, assignee, start_date, end_date, status, progress, priority, remark, sort_order, writer, company_code)
|
||
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13) RETURNING *`,
|
||
|
|
[projectId, name, category, assignee, start_date, end_date, status || "대기", progress || "0", priority || "보통", remark, sort_order || "0", userId, companyCode]
|
||
|
|
);
|
||
|
|
res.json({ success: true, data: result[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("태스크 생성 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function updateTask(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { taskId } = req.params;
|
||
|
|
const { name, category, assignee, start_date, end_date, status, progress, priority, remark, sort_order } = req.body;
|
||
|
|
|
||
|
|
const conditions = [`id = $1`];
|
||
|
|
const params: any[] = [taskId];
|
||
|
|
let pi = 2;
|
||
|
|
if (companyCode !== "*") { conditions.push(`company_code = $${pi}`); params.push(companyCode); pi++; }
|
||
|
|
|
||
|
|
const sets: string[] = [];
|
||
|
|
const fields: Record<string, any> = { name, category, assignee, start_date, end_date, status, progress, priority, remark, sort_order };
|
||
|
|
for (const [key, val] of Object.entries(fields)) {
|
||
|
|
if (val !== undefined) { sets.push(`${key} = $${pi}`); params.push(val); pi++; }
|
||
|
|
}
|
||
|
|
sets.push(`updated_date = now()`);
|
||
|
|
|
||
|
|
const result = await query(`UPDATE dsn_project_task SET ${sets.join(", ")} WHERE ${conditions.join(" AND ")} RETURNING *`, params);
|
||
|
|
if (!result.length) { res.status(404).json({ success: false, message: "태스크를 찾을 수 없습니다." }); return; }
|
||
|
|
res.json({ success: true, data: result[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("태스크 수정 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function deleteTask(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { taskId } = req.params;
|
||
|
|
const conditions = [`id = $1`];
|
||
|
|
const params: any[] = [taskId];
|
||
|
|
if (companyCode !== "*") { conditions.push(`company_code = $2`); params.push(companyCode); }
|
||
|
|
|
||
|
|
const result = await query(`DELETE FROM dsn_project_task WHERE ${conditions.join(" AND ")} RETURNING id`, params);
|
||
|
|
if (!result.length) { res.status(404).json({ success: false, message: "태스크를 찾을 수 없습니다." }); return; }
|
||
|
|
res.json({ success: true });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("태스크 삭제 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================
|
||
|
|
// 작업일지 CRUD
|
||
|
|
// ============================================
|
||
|
|
|
||
|
|
export async function getWorkLogsByTask(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { taskId } = req.params;
|
||
|
|
|
||
|
|
const conditions = [`w.task_id = $1`];
|
||
|
|
const params: any[] = [taskId];
|
||
|
|
if (companyCode !== "*") { conditions.push(`w.company_code = $2`); params.push(companyCode); }
|
||
|
|
|
||
|
|
const sql = `
|
||
|
|
SELECT w.*,
|
||
|
|
COALESCE((SELECT json_agg(json_build_object('id', a.id, 'file_name', a.file_name, 'file_type', a.file_type, 'file_size', a.file_size)) FROM dsn_work_attachment a WHERE a.work_log_id = w.id), '[]') AS attachments,
|
||
|
|
COALESCE((SELECT json_agg(json_build_object('id', p.id, 'item', p.item, 'qty', p.qty, 'unit', p.unit, 'reason', p.reason, 'status', p.status)) FROM dsn_purchase_req p WHERE p.work_log_id = w.id), '[]') AS purchase_reqs,
|
||
|
|
COALESCE((SELECT json_agg(json_build_object(
|
||
|
|
'id', c.id, 'to_user', c.to_user, 'to_dept', c.to_dept, 'title', c.title, 'description', c.description, 'status', c.status, 'due_date', c.due_date,
|
||
|
|
'responses', COALESCE((SELECT json_agg(json_build_object('id', cr.id, 'response_date', cr.response_date, 'user_name', cr.user_name, 'content', cr.content)) FROM dsn_coop_response cr WHERE cr.coop_req_id = c.id), '[]')
|
||
|
|
)) FROM dsn_coop_req c WHERE c.work_log_id = w.id), '[]') AS coop_reqs
|
||
|
|
FROM dsn_work_log w
|
||
|
|
WHERE ${conditions.join(" AND ")}
|
||
|
|
ORDER BY w.start_dt DESC
|
||
|
|
`;
|
||
|
|
const result = await query(sql, params);
|
||
|
|
res.json({ success: true, data: result });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("작업일지 조회 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function createWorkLog(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const userId = req.user!.userId;
|
||
|
|
const { taskId } = req.params;
|
||
|
|
const { start_dt, end_dt, hours, description, progress_before, progress_after, author, sub_item_id } = req.body;
|
||
|
|
|
||
|
|
const result = await query(
|
||
|
|
`INSERT INTO dsn_work_log (task_id, start_dt, end_dt, hours, description, progress_before, progress_after, author, sub_item_id, writer, company_code)
|
||
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) RETURNING *`,
|
||
|
|
[taskId, start_dt, end_dt, hours || "0", description, progress_before || "0", progress_after || "0", author, sub_item_id, userId, companyCode]
|
||
|
|
);
|
||
|
|
res.json({ success: true, data: result[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("작업일지 생성 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function deleteWorkLog(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { workLogId } = req.params;
|
||
|
|
const conditions = [`id = $1`];
|
||
|
|
const params: any[] = [workLogId];
|
||
|
|
if (companyCode !== "*") { conditions.push(`company_code = $2`); params.push(companyCode); }
|
||
|
|
|
||
|
|
const result = await query(`DELETE FROM dsn_work_log WHERE ${conditions.join(" AND ")} RETURNING id`, params);
|
||
|
|
if (!result.length) { res.status(404).json({ success: false, message: "작업일지를 찾을 수 없습니다." }); return; }
|
||
|
|
res.json({ success: true });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("작업일지 삭제 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================
|
||
|
|
// 태스크 하위항목 CRUD
|
||
|
|
// ============================================
|
||
|
|
|
||
|
|
export async function createSubItem(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const userId = req.user!.userId;
|
||
|
|
const { taskId } = req.params;
|
||
|
|
const { name, weight, progress, status } = req.body;
|
||
|
|
|
||
|
|
const result = await query(
|
||
|
|
`INSERT INTO dsn_task_sub_item (task_id, name, weight, progress, status, writer, company_code) VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING *`,
|
||
|
|
[taskId, name, weight || "0", progress || "0", status || "대기", userId, companyCode]
|
||
|
|
);
|
||
|
|
res.json({ success: true, data: result[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("하위항목 생성 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function updateSubItem(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { subItemId } = req.params;
|
||
|
|
const { name, weight, progress, status } = req.body;
|
||
|
|
|
||
|
|
const conditions = [`id = $1`];
|
||
|
|
const params: any[] = [subItemId];
|
||
|
|
let pi = 2;
|
||
|
|
if (companyCode !== "*") { conditions.push(`company_code = $${pi}`); params.push(companyCode); pi++; }
|
||
|
|
|
||
|
|
const sets: string[] = [];
|
||
|
|
const fields: Record<string, any> = { name, weight, progress, status };
|
||
|
|
for (const [key, val] of Object.entries(fields)) {
|
||
|
|
if (val !== undefined) { sets.push(`${key} = $${pi}`); params.push(val); pi++; }
|
||
|
|
}
|
||
|
|
sets.push(`updated_date = now()`);
|
||
|
|
|
||
|
|
const result = await query(`UPDATE dsn_task_sub_item SET ${sets.join(", ")} WHERE ${conditions.join(" AND ")} RETURNING *`, params);
|
||
|
|
if (!result.length) { res.status(404).json({ success: false, message: "하위항목을 찾을 수 없습니다." }); return; }
|
||
|
|
res.json({ success: true, data: result[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("하위항목 수정 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function deleteSubItem(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { subItemId } = req.params;
|
||
|
|
const conditions = [`id = $1`];
|
||
|
|
const params: any[] = [subItemId];
|
||
|
|
if (companyCode !== "*") { conditions.push(`company_code = $2`); params.push(companyCode); }
|
||
|
|
|
||
|
|
const result = await query(`DELETE FROM dsn_task_sub_item WHERE ${conditions.join(" AND ")} RETURNING id`, params);
|
||
|
|
if (!result.length) { res.status(404).json({ success: false, message: "하위항목을 찾을 수 없습니다." }); return; }
|
||
|
|
res.json({ success: true });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("하위항목 삭제 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================
|
||
|
|
// 태스크 이슈 CRUD
|
||
|
|
// ============================================
|
||
|
|
|
||
|
|
export async function createIssue(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const userId = req.user!.userId;
|
||
|
|
const { taskId } = req.params;
|
||
|
|
const { title, status, priority, description, registered_by, registered_date } = req.body;
|
||
|
|
|
||
|
|
const result = await query(
|
||
|
|
`INSERT INTO dsn_task_issue (task_id, title, status, priority, description, registered_by, registered_date, writer, company_code)
|
||
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING *`,
|
||
|
|
[taskId, title, status || "등록", priority || "보통", description, registered_by, registered_date, userId, companyCode]
|
||
|
|
);
|
||
|
|
res.json({ success: true, data: result[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("이슈 생성 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function updateIssue(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { issueId } = req.params;
|
||
|
|
const { title, status, priority, description, resolved_date } = req.body;
|
||
|
|
|
||
|
|
const conditions = [`id = $1`];
|
||
|
|
const params: any[] = [issueId];
|
||
|
|
let pi = 2;
|
||
|
|
if (companyCode !== "*") { conditions.push(`company_code = $${pi}`); params.push(companyCode); pi++; }
|
||
|
|
|
||
|
|
const sets: string[] = [];
|
||
|
|
const fields: Record<string, any> = { title, status, priority, description, resolved_date };
|
||
|
|
for (const [key, val] of Object.entries(fields)) {
|
||
|
|
if (val !== undefined) { sets.push(`${key} = $${pi}`); params.push(val); pi++; }
|
||
|
|
}
|
||
|
|
sets.push(`updated_date = now()`);
|
||
|
|
|
||
|
|
const result = await query(`UPDATE dsn_task_issue SET ${sets.join(", ")} WHERE ${conditions.join(" AND ")} RETURNING *`, params);
|
||
|
|
if (!result.length) { res.status(404).json({ success: false, message: "이슈를 찾을 수 없습니다." }); return; }
|
||
|
|
res.json({ success: true, data: result[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("이슈 수정 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================
|
||
|
|
// ECN (설변통보) CRUD
|
||
|
|
// ============================================
|
||
|
|
|
||
|
|
export async function getEcnList(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { status, search } = req.query;
|
||
|
|
|
||
|
|
const conditions: string[] = [];
|
||
|
|
const params: any[] = [];
|
||
|
|
let pi = 1;
|
||
|
|
|
||
|
|
if (companyCode !== "*") { conditions.push(`e.company_code = $${pi}`); params.push(companyCode); pi++; }
|
||
|
|
if (status) { conditions.push(`e.status = $${pi}`); params.push(status); pi++; }
|
||
|
|
if (search) {
|
||
|
|
conditions.push(`(e.ecn_no ILIKE $${pi} OR e.target ILIKE $${pi})`);
|
||
|
|
params.push(`%${search}%`);
|
||
|
|
pi++;
|
||
|
|
}
|
||
|
|
|
||
|
|
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
||
|
|
const sql = `
|
||
|
|
SELECT e.*,
|
||
|
|
COALESCE((SELECT json_agg(json_build_object('id', h.id, 'status', h.status, 'history_date', h.history_date, 'user_name', h.user_name, 'description', h.description) ORDER BY h.created_date) FROM dsn_ecn_history h WHERE h.ecn_id = e.id), '[]') AS history,
|
||
|
|
COALESCE((SELECT json_agg(nd.dept_name) FROM dsn_ecn_notify_dept nd WHERE nd.ecn_id = e.id), '[]') AS notify_depts
|
||
|
|
FROM dsn_ecn e
|
||
|
|
${where}
|
||
|
|
ORDER BY e.created_date DESC
|
||
|
|
`;
|
||
|
|
const result = await query(sql, params);
|
||
|
|
res.json({ success: true, data: result });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("ECN 목록 조회 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function createEcn(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
const pool = getPool();
|
||
|
|
const client = await pool.connect();
|
||
|
|
try {
|
||
|
|
await client.query("BEGIN");
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const userId = req.user!.userId;
|
||
|
|
const { ecn_no, ecr_id, ecn_date, apply_date, status, target, drawing_before, drawing_after, designer, before_content, after_content, reason, remark, notify_depts, history } = req.body;
|
||
|
|
|
||
|
|
const result = await client.query(
|
||
|
|
`INSERT INTO dsn_ecn (ecn_no, ecr_id, ecn_date, apply_date, status, target, drawing_before, drawing_after, designer, before_content, after_content, reason, remark, writer, company_code)
|
||
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15) RETURNING *`,
|
||
|
|
[ecn_no, ecr_id, ecn_date, apply_date, status || "ECN발행", target, drawing_before, drawing_after, designer, before_content, after_content, reason, remark, userId, companyCode]
|
||
|
|
);
|
||
|
|
|
||
|
|
const ecnId = result.rows[0].id;
|
||
|
|
|
||
|
|
if (notify_depts?.length) {
|
||
|
|
for (const dept of notify_depts) {
|
||
|
|
await client.query(`INSERT INTO dsn_ecn_notify_dept (ecn_id, dept_name, writer, company_code) VALUES ($1,$2,$3,$4)`, [ecnId, dept, userId, companyCode]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (history?.length) {
|
||
|
|
for (const h of history) {
|
||
|
|
await client.query(
|
||
|
|
`INSERT INTO dsn_ecn_history (ecn_id, status, history_date, user_name, description, writer, company_code) VALUES ($1,$2,$3,$4,$5,$6,$7)`,
|
||
|
|
[ecnId, h.status, h.history_date, h.user_name, h.description, userId, companyCode]
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
await client.query("COMMIT");
|
||
|
|
res.json({ success: true, data: result.rows[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
await client.query("ROLLBACK");
|
||
|
|
logger.error("ECN 생성 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
} finally {
|
||
|
|
client.release();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function updateEcn(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
const pool = getPool();
|
||
|
|
const client = await pool.connect();
|
||
|
|
try {
|
||
|
|
await client.query("BEGIN");
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const userId = req.user!.userId;
|
||
|
|
const { id } = req.params;
|
||
|
|
const { ecn_no, ecn_date, apply_date, status, target, drawing_before, drawing_after, designer, before_content, after_content, reason, remark, notify_depts, history } = req.body;
|
||
|
|
|
||
|
|
const conditions = [`id = $1`];
|
||
|
|
const params: any[] = [id];
|
||
|
|
let pi = 2;
|
||
|
|
if (companyCode !== "*") { conditions.push(`company_code = $${pi}`); params.push(companyCode); pi++; }
|
||
|
|
|
||
|
|
const sets: string[] = [];
|
||
|
|
const fields: Record<string, any> = { ecn_no, ecn_date, apply_date, status, target, drawing_before, drawing_after, designer, before_content, after_content, reason, remark };
|
||
|
|
for (const [key, val] of Object.entries(fields)) {
|
||
|
|
if (val !== undefined) { sets.push(`${key} = $${pi}`); params.push(val); pi++; }
|
||
|
|
}
|
||
|
|
sets.push(`updated_date = now()`);
|
||
|
|
|
||
|
|
const result = await client.query(`UPDATE dsn_ecn SET ${sets.join(", ")} WHERE ${conditions.join(" AND ")} RETURNING *`, params);
|
||
|
|
if (!result.rowCount) { await client.query("ROLLBACK"); res.status(404).json({ success: false, message: "ECN을 찾을 수 없습니다." }); return; }
|
||
|
|
|
||
|
|
if (notify_depts !== undefined) {
|
||
|
|
await client.query(`DELETE FROM dsn_ecn_notify_dept WHERE ecn_id = $1`, [id]);
|
||
|
|
for (const dept of notify_depts) {
|
||
|
|
await client.query(`INSERT INTO dsn_ecn_notify_dept (ecn_id, dept_name, writer, company_code) VALUES ($1,$2,$3,$4)`, [id, dept, userId, companyCode]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (history !== undefined) {
|
||
|
|
await client.query(`DELETE FROM dsn_ecn_history WHERE ecn_id = $1`, [id]);
|
||
|
|
for (const h of history) {
|
||
|
|
await client.query(
|
||
|
|
`INSERT INTO dsn_ecn_history (ecn_id, status, history_date, user_name, description, writer, company_code) VALUES ($1,$2,$3,$4,$5,$6,$7)`,
|
||
|
|
[id, h.status, h.history_date, h.user_name, h.description, userId, companyCode]
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
await client.query("COMMIT");
|
||
|
|
res.json({ success: true, data: result.rows[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
await client.query("ROLLBACK");
|
||
|
|
logger.error("ECN 수정 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
} finally {
|
||
|
|
client.release();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function deleteEcn(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const { id } = req.params;
|
||
|
|
const conditions = [`id = $1`];
|
||
|
|
const params: any[] = [id];
|
||
|
|
if (companyCode !== "*") { conditions.push(`company_code = $2`); params.push(companyCode); }
|
||
|
|
|
||
|
|
const result = await query(`DELETE FROM dsn_ecn WHERE ${conditions.join(" AND ")} RETURNING id`, params);
|
||
|
|
if (!result.length) { res.status(404).json({ success: false, message: "ECN을 찾을 수 없습니다." }); return; }
|
||
|
|
res.json({ success: true });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("ECN 삭제 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================
|
||
|
|
// 나의 업무 (My Work) - 로그인 사용자 기준
|
||
|
|
// ============================================
|
||
|
|
|
||
|
|
export async function getMyWork(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const userName = req.user!.userName;
|
||
|
|
const { status, project_id } = req.query;
|
||
|
|
|
||
|
|
const conditions = [`t.assignee = $1`];
|
||
|
|
const params: any[] = [userName];
|
||
|
|
let pi = 2;
|
||
|
|
|
||
|
|
if (companyCode !== "*") { conditions.push(`t.company_code = $${pi}`); params.push(companyCode); pi++; }
|
||
|
|
if (status) { conditions.push(`t.status = $${pi}`); params.push(status); pi++; }
|
||
|
|
if (project_id) { conditions.push(`t.project_id = $${pi}`); params.push(project_id); pi++; }
|
||
|
|
|
||
|
|
const sql = `
|
||
|
|
SELECT t.*,
|
||
|
|
p.project_no, p.name AS project_name, p.customer AS project_customer, p.status AS project_status,
|
||
|
|
COALESCE((SELECT json_agg(json_build_object('id', s.id, 'name', s.name, 'weight', s.weight, 'progress', s.progress, 'status', s.status) ORDER BY s.created_date) FROM dsn_task_sub_item s WHERE s.task_id = t.id), '[]') AS sub_items,
|
||
|
|
COALESCE((SELECT json_agg(json_build_object(
|
||
|
|
'id', w.id, 'start_dt', w.start_dt, 'end_dt', w.end_dt, 'hours', w.hours, 'description', w.description, 'sub_item_id', w.sub_item_id,
|
||
|
|
'attachments', COALESCE((SELECT json_agg(json_build_object('id', a.id, 'file_name', a.file_name, 'file_type', a.file_type, 'file_size', a.file_size)) FROM dsn_work_attachment a WHERE a.work_log_id = w.id), '[]'),
|
||
|
|
'purchase_reqs', COALESCE((SELECT json_agg(json_build_object('id', pr.id, 'item', pr.item, 'qty', pr.qty, 'unit', pr.unit, 'reason', pr.reason, 'status', pr.status)) FROM dsn_purchase_req pr WHERE pr.work_log_id = w.id), '[]'),
|
||
|
|
'coop_reqs', COALESCE((SELECT json_agg(json_build_object(
|
||
|
|
'id', c.id, 'to_user', c.to_user, 'to_dept', c.to_dept, 'title', c.title, 'description', c.description, 'status', c.status, 'due_date', c.due_date,
|
||
|
|
'responses', COALESCE((SELECT json_agg(json_build_object('id', cr.id, 'response_date', cr.response_date, 'user_name', cr.user_name, 'content', cr.content)) FROM dsn_coop_response cr WHERE cr.coop_req_id = c.id), '[]')
|
||
|
|
)) FROM dsn_coop_req c WHERE c.work_log_id = w.id), '[]')
|
||
|
|
) ORDER BY w.start_dt DESC) FROM dsn_work_log w WHERE w.task_id = t.id), '[]') AS work_logs
|
||
|
|
FROM dsn_project_task t
|
||
|
|
JOIN dsn_project p ON p.id = t.project_id
|
||
|
|
WHERE ${conditions.join(" AND ")}
|
||
|
|
ORDER BY
|
||
|
|
CASE t.status WHEN '진행중' THEN 1 WHEN '대기' THEN 2 WHEN '검토중' THEN 3 ELSE 4 END,
|
||
|
|
t.end_date ASC
|
||
|
|
`;
|
||
|
|
const result = await query(sql, params);
|
||
|
|
res.json({ success: true, data: result });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("나의 업무 조회 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ============================================
|
||
|
|
// 구매요청 / 협업요청 CRUD (my-work에서 사용)
|
||
|
|
// ============================================
|
||
|
|
|
||
|
|
export async function createPurchaseReq(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const userId = req.user!.userId;
|
||
|
|
const { workLogId } = req.params;
|
||
|
|
const { item, qty, unit, reason, status } = req.body;
|
||
|
|
|
||
|
|
const result = await query(
|
||
|
|
`INSERT INTO dsn_purchase_req (work_log_id, item, qty, unit, reason, status, writer, company_code) VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING *`,
|
||
|
|
[workLogId, item, qty, unit, reason, status || "요청", userId, companyCode]
|
||
|
|
);
|
||
|
|
res.json({ success: true, data: result[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("구매요청 생성 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function createCoopReq(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const userId = req.user!.userId;
|
||
|
|
const { workLogId } = req.params;
|
||
|
|
const { to_user, to_dept, title, description, due_date } = req.body;
|
||
|
|
|
||
|
|
const result = await query(
|
||
|
|
`INSERT INTO dsn_coop_req (work_log_id, to_user, to_dept, title, description, status, due_date, writer, company_code) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING *`,
|
||
|
|
[workLogId, to_user, to_dept, title, description, "요청", due_date, userId, companyCode]
|
||
|
|
);
|
||
|
|
res.json({ success: true, data: result[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("협업요청 생성 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function addCoopResponse(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||
|
|
try {
|
||
|
|
const companyCode = req.user!.companyCode!;
|
||
|
|
const userId = req.user!.userId;
|
||
|
|
const { coopReqId } = req.params;
|
||
|
|
const { response_date, user_name, content } = req.body;
|
||
|
|
|
||
|
|
const result = await query(
|
||
|
|
`INSERT INTO dsn_coop_response (coop_req_id, response_date, user_name, content, writer, company_code) VALUES ($1,$2,$3,$4,$5,$6) RETURNING *`,
|
||
|
|
[coopReqId, response_date, user_name, content, userId, companyCode]
|
||
|
|
);
|
||
|
|
res.json({ success: true, data: result[0] });
|
||
|
|
} catch (error: any) {
|
||
|
|
logger.error("협업응답 추가 오류", error);
|
||
|
|
res.status(500).json({ success: false, message: error.message });
|
||
|
|
}
|
||
|
|
}
|