feat: React 프론트엔드 기능 대폭 확장

- 월별근무표: 휴일/근무일 관리, 자동 초기화
- 메일양식: 템플릿 CRUD, To/CC/BCC 설정
- 그룹정보: 부서 관리, 비트 연산 기반 권한 설정
- 업무일지: 수정 성공 메시지 제거, 오늘 근무시간 필터링 수정
- 웹소켓 메시지 type 충돌 버그 수정

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
backuppc
2025-11-27 17:25:31 +09:00
parent b57af6dad7
commit c9b5d756e1
65 changed files with 14028 additions and 467 deletions

View File

@@ -12,62 +12,47 @@ namespace Project.Web
#region Jobreport API
/// <summary>
/// 업무일지 목록 조회
/// 업무일지 목록 조회 (vJobReportForUser 뷰 사용)
/// </summary>
public string Jobreport_GetList(string sd, string ed, string uid, string cate, string doit)
public string Jobreport_GetList(string sd, string ed, string uid, string cate, string searchKey)
{
try
{
var sql = @"SELECT j.idx, j.jdate, j.uid, j.cate, j.title, j.doit, j.remark, j.jfrom, j.jto,
u.name as userName, j.wdate
FROM EETGW_Jobreport j WITH (nolock)
LEFT JOIN Users u ON j.uid = u.id
WHERE j.gcode = @gcode";
var sql = @"SELECT idx, pidx, pdate, id, name,type, svalue, hrs,ot,requestpart,package,userprocess,status, projectName, description, ww,otpms,process
FROM vJobReportForUser WITH (nolock)
WHERE gcode = @gcode AND (pdate BETWEEN @sd AND @ed)";
var parameters = new List<SqlParameter>();
parameters.Add(new SqlParameter("@gcode", info.Login.gcode));
parameters.Add(new SqlParameter("@sd", sd));
parameters.Add(new SqlParameter("@ed", ed));
if (!string.IsNullOrEmpty(sd))
{
sql += " AND j.jdate >= @sd";
parameters.Add(new SqlParameter("@sd", sd));
}
if (!string.IsNullOrEmpty(ed))
{
sql += " AND j.jdate <= @ed";
parameters.Add(new SqlParameter("@ed", ed));
}
if (!string.IsNullOrEmpty(uid))
{
sql += " AND j.uid = @uid";
sql += " AND id = @uid";
parameters.Add(new SqlParameter("@uid", uid));
}
if (!string.IsNullOrEmpty(cate))
if (!string.IsNullOrEmpty(searchKey))
{
sql += " AND j.cate = @cate";
parameters.Add(new SqlParameter("@cate", cate));
}
if (!string.IsNullOrEmpty(doit))
{
sql += " AND j.doit = @doit";
parameters.Add(new SqlParameter("@doit", doit));
sql += " AND (requestpart LIKE @searchKey OR package LIKE @searchKey OR projectName LIKE @searchKey OR process LIKE @searchKey OR [type] LIKE @searchKey OR description LIKE @searchKey)";
parameters.Add(new SqlParameter("@searchKey", "%" + searchKey + "%"));
}
sql += " ORDER BY j.jdate DESC, j.idx DESC";
sql += " ORDER BY pdate DESC, idx DESC";
var cs = Properties.Settings.Default.gwcs;
var cn = new SqlConnection(cs);
var cmd = new SqlCommand(sql, cn);
cmd.Parameters.AddRange(parameters.ToArray());
var da = new SqlDataAdapter(cmd);
var dt = new DataTable();
da.Fill(dt);
da.Dispose();
cmd.Dispose();
cn.Dispose();
return JsonConvert.SerializeObject(new { Success = true, Data = dt }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
using (var cn = new SqlConnection(cs))
using (var cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.AddRange(parameters.ToArray());
using (var da = new SqlDataAdapter(cmd))
{
var dt = new DataTable();
da.Fill(dt);
return JsonConvert.SerializeObject(new { Success = true, Data = dt });
}
}
}
catch (Exception ex)
{
@@ -88,18 +73,17 @@ namespace Project.Web
ORDER BY u.name";
var cs = Properties.Settings.Default.gwcs;
var cn = new SqlConnection(cs);
var cmd = new SqlCommand(sql, cn);
cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
var da = new SqlDataAdapter(cmd);
var dt = new DataTable();
da.Fill(dt);
da.Dispose();
cmd.Dispose();
cn.Dispose();
return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
using (var cn = new SqlConnection(cs))
using (var cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
using (var da = new SqlDataAdapter(cmd))
{
var dt = new DataTable();
da.Fill(dt);
return JsonConvert.SerializeObject(dt);
}
}
}
catch (Exception ex)
{
@@ -109,36 +93,40 @@ namespace Project.Web
}
/// <summary>
/// 업무일지 상세 조회
/// 업무일지 상세 조회 (vJobReportForUser 뷰 사용)
/// </summary>
public string Jobreport_GetDetail(int id)
{
try
{
var sql = @"SELECT j.*, u.name as userName
FROM EETGW_Jobreport j WITH (nolock)
LEFT JOIN Users u ON j.uid = u.id
WHERE j.idx = @idx AND j.gcode = @gcode";
var sql = @"SELECT idx, pidx, pdate, id, name, type, svalue, hrs, ot, requestpart, package,
userprocess, status, projectName, description, ww, otpms, process
FROM vJobReportForUser WITH (nolock)
WHERE idx = @idx AND gcode = @gcode";
var cs = Properties.Settings.Default.gwcs;
var cn = new SqlConnection(cs);
var cmd = new SqlCommand(sql, cn);
cmd.Parameters.AddWithValue("@idx", id);
cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
var da = new SqlDataAdapter(cmd);
var dt = new DataTable();
da.Fill(dt);
da.Dispose();
cmd.Dispose();
cn.Dispose();
if (dt.Rows.Count > 0)
using (var cn = new SqlConnection(cs))
using (var cmd = new SqlCommand(sql, cn))
{
return JsonConvert.SerializeObject(new { Success = true, Data = dt.Rows[0] }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
cmd.Parameters.AddWithValue("@idx", id);
cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
using (var da = new SqlDataAdapter(cmd))
{
var dt = new DataTable();
da.Fill(dt);
if (dt.Rows.Count > 0)
{
var row = dt.Rows[0];
var data = new Dictionary<string, object>();
foreach (DataColumn col in dt.Columns)
{
data[col.ColumnName] = row[col] == DBNull.Value ? null : row[col];
}
return JsonConvert.SerializeObject(new { Success = true, Data = data });
}
return JsonConvert.SerializeObject(new { Success = false, Message = "데이터를 찾을 수 없습니다." });
}
}
return JsonConvert.SerializeObject(new { Success = false, Message = "데이터를 찾을 수 없습니다." });
}
catch (Exception ex)
{
@@ -147,37 +135,50 @@ namespace Project.Web
}
/// <summary>
/// 업무일지 추가
/// 업무일지 추가 (JobReport 테이블)
/// </summary>
public string Jobreport_Add(string jdate, string cate, string title, string doit, string remark, string jfrom, string jto)
public string Jobreport_Add(string pdate, string projectName, string requestpart, string package,
string type, string process, string status, string description, double hrs, double ot, string jobgrp, string tag)
{
try
{
var sql = @"INSERT INTO EETGW_Jobreport (gcode, uid, jdate, cate, title, doit, remark, jfrom, jto, wuid, wdate)
VALUES (@gcode, @uid, @jdate, @cate, @title, @doit, @remark, @jfrom, @jto, @wuid, GETDATE());
// 마감 체크
var smon = pdate.Substring(0, 7);
if (DBM.GetMagamStatus(smon))
{
return JsonConvert.SerializeObject(new { Success = false, Message = $"등록일이 속한 월({smon})이 마감되었습니다." });
}
var sql = @"INSERT INTO JobReport (gcode, uid, pdate, projectName, requestpart, package,
type, process, status, description, hrs, ot, jobgrp, tag, wuid, wdate, pidx)
VALUES (@gcode, @uid, @pdate, @projectName, @requestpart, @package,
@type, @process, @status, @description, @hrs, @ot, @jobgrp, @tag, @wuid, GETDATE(), -1);
SELECT SCOPE_IDENTITY();";
var cs = Properties.Settings.Default.gwcs;
var cn = new SqlConnection(cs);
var cmd = new SqlCommand(sql, cn);
cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
cmd.Parameters.AddWithValue("@uid", info.Login.no);
cmd.Parameters.AddWithValue("@jdate", jdate ?? DateTime.Now.ToString("yyyy-MM-dd"));
cmd.Parameters.AddWithValue("@cate", cate ?? "");
cmd.Parameters.AddWithValue("@title", title ?? "");
cmd.Parameters.AddWithValue("@doit", doit ?? "");
cmd.Parameters.AddWithValue("@remark", remark ?? "");
cmd.Parameters.AddWithValue("@jfrom", jfrom ?? "");
cmd.Parameters.AddWithValue("@jto", jto ?? "");
cmd.Parameters.AddWithValue("@wuid", info.Login.no);
using (var cn = new SqlConnection(cs))
using (var cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
cmd.Parameters.AddWithValue("@uid", info.Login.no);
cmd.Parameters.AddWithValue("@pdate", pdate);
cmd.Parameters.AddWithValue("@projectName", projectName ?? "");
cmd.Parameters.AddWithValue("@requestpart", requestpart ?? "");
cmd.Parameters.AddWithValue("@package", package ?? "");
cmd.Parameters.AddWithValue("@type", type ?? "");
cmd.Parameters.AddWithValue("@process", process ?? "");
cmd.Parameters.AddWithValue("@status", status ?? "진행 완료");
cmd.Parameters.AddWithValue("@description", description ?? "");
cmd.Parameters.AddWithValue("@hrs", hrs);
cmd.Parameters.AddWithValue("@ot", ot);
cmd.Parameters.AddWithValue("@jobgrp", jobgrp ?? "");
cmd.Parameters.AddWithValue("@tag", tag ?? "");
cmd.Parameters.AddWithValue("@wuid", info.Login.no);
cn.Open();
var newId = Convert.ToInt32(cmd.ExecuteScalar());
cn.Close();
cmd.Dispose();
cn.Dispose();
return JsonConvert.SerializeObject(new { Success = true, Message = "저장되었습니다.", Data = new { idx = newId } });
cn.Open();
var newId = Convert.ToInt32(cmd.ExecuteScalar());
return JsonConvert.SerializeObject(new { Success = true, Message = "저장되었습니다.", Data = new { idx = newId } });
}
}
catch (Exception ex)
{
@@ -186,39 +187,73 @@ namespace Project.Web
}
/// <summary>
/// 업무일지 수정
/// 업무일지 수정 (JobReport 테이블)
/// </summary>
public string Jobreport_Edit(int idx, string jdate, string cate, string title, string doit, string remark, string jfrom, string jto)
public string Jobreport_Edit(int idx, string pdate, string projectName, string requestpart, string package,
string type, string process, string status, string description, double hrs, double ot, string jobgrp, string tag)
{
try
{
var sql = @"UPDATE EETGW_Jobreport SET
jdate = @jdate, cate = @cate, title = @title, doit = @doit,
remark = @remark, jfrom = @jfrom, jto = @jto,
// 권한 체크
int curLevel = Math.Max(info.Login.level, DBM.getAuth(DBM.eAuthType.jobreport));
// 마감 체크
var smon = pdate.Substring(0, 7);
if (DBM.GetMagamStatus(smon))
{
return JsonConvert.SerializeObject(new { Success = false, Message = $"등록일이 속한 월({smon})이 마감되었습니다." });
}
// 본인 자료인지 체크 (관리자가 아닌 경우)
if (curLevel < 5)
{
var checkSql = "SELECT uid FROM JobReport WHERE idx = @idx AND gcode = @gcode";
var cs2 = Properties.Settings.Default.gwcs;
using (var cn2 = new SqlConnection(cs2))
using (var cmd2 = new SqlCommand(checkSql, cn2))
{
cmd2.Parameters.AddWithValue("@idx", idx);
cmd2.Parameters.AddWithValue("@gcode", info.Login.gcode);
cn2.Open();
var ownerUid = cmd2.ExecuteScalar()?.ToString();
if (ownerUid != info.Login.no)
{
return JsonConvert.SerializeObject(new { Success = false, Message = "타인의 자료는 수정할 수 없습니다." });
}
}
}
var sql = @"UPDATE JobReport SET
pdate = @pdate, projectName = @projectName, requestpart = @requestpart,
package = @package, type = @type, process = @process, status = @status,
description = @description, hrs = @hrs, ot = @ot, jobgrp = @jobgrp, tag = @tag,
wuid = @wuid, wdate = GETDATE()
WHERE idx = @idx AND gcode = @gcode";
var cs = Properties.Settings.Default.gwcs;
var cn = new SqlConnection(cs);
var cmd = new SqlCommand(sql, cn);
cmd.Parameters.AddWithValue("@idx", idx);
cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
cmd.Parameters.AddWithValue("@jdate", jdate ?? DateTime.Now.ToString("yyyy-MM-dd"));
cmd.Parameters.AddWithValue("@cate", cate ?? "");
cmd.Parameters.AddWithValue("@title", title ?? "");
cmd.Parameters.AddWithValue("@doit", doit ?? "");
cmd.Parameters.AddWithValue("@remark", remark ?? "");
cmd.Parameters.AddWithValue("@jfrom", jfrom ?? "");
cmd.Parameters.AddWithValue("@jto", jto ?? "");
cmd.Parameters.AddWithValue("@wuid", info.Login.no);
using (var cn = new SqlConnection(cs))
using (var cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.AddWithValue("@idx", idx);
cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
cmd.Parameters.AddWithValue("@pdate", pdate);
cmd.Parameters.AddWithValue("@projectName", projectName ?? "");
cmd.Parameters.AddWithValue("@requestpart", requestpart ?? "");
cmd.Parameters.AddWithValue("@package", package ?? "");
cmd.Parameters.AddWithValue("@type", type ?? "");
cmd.Parameters.AddWithValue("@process", process ?? "");
cmd.Parameters.AddWithValue("@status", status ?? "");
cmd.Parameters.AddWithValue("@description", description ?? "");
cmd.Parameters.AddWithValue("@hrs", hrs);
cmd.Parameters.AddWithValue("@ot", ot);
cmd.Parameters.AddWithValue("@jobgrp", jobgrp ?? "");
cmd.Parameters.AddWithValue("@tag", tag ?? "");
cmd.Parameters.AddWithValue("@wuid", info.Login.no);
cn.Open();
var result = cmd.ExecuteNonQuery();
cn.Close();
cmd.Dispose();
cn.Dispose();
return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "수정되었습니다." : "수정에 실패했습니다." });
cn.Open();
var result = cmd.ExecuteNonQuery();
return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "수정되었습니다." : "수정에 실패했습니다." });
}
}
catch (Exception ex)
{
@@ -233,21 +268,128 @@ namespace Project.Web
{
try
{
var sql = "DELETE FROM EETGW_Jobreport WHERE idx = @idx AND gcode = @gcode";
// 권한 체크
int curLevel = Math.Max(info.Login.level, DBM.getAuth(DBM.eAuthType.jobreport));
// 본인 자료인지 체크 (관리자가 아닌 경우)
if (curLevel < 5)
{
var checkSql = "SELECT uid, pdate FROM JobReport WHERE idx = @idx AND gcode = @gcode";
var cs2 = Properties.Settings.Default.gwcs;
using (var cn2 = new SqlConnection(cs2))
using (var cmd2 = new SqlCommand(checkSql, cn2))
{
cmd2.Parameters.AddWithValue("@idx", idx);
cmd2.Parameters.AddWithValue("@gcode", info.Login.gcode);
cn2.Open();
using (var reader = cmd2.ExecuteReader())
{
if (reader.Read())
{
var ownerUid = reader["uid"]?.ToString();
var pdate = reader["pdate"]?.ToString();
if (ownerUid != info.Login.no)
{
return JsonConvert.SerializeObject(new { Success = false, Message = "타인의 자료는 삭제할 수 없습니다." });
}
// 마감 체크
if (!string.IsNullOrEmpty(pdate) && pdate.Length >= 7)
{
var smon = pdate.Substring(0, 7);
if (DBM.GetMagamStatus(smon))
{
return JsonConvert.SerializeObject(new { Success = false, Message = $"등록일이 속한 월({smon})이 마감되었습니다." });
}
}
}
}
}
}
var sql = "DELETE FROM JobReport WHERE idx = @idx AND gcode = @gcode";
var cs = Properties.Settings.Default.gwcs;
var cn = new SqlConnection(cs);
var cmd = new SqlCommand(sql, cn);
cmd.Parameters.AddWithValue("@idx", idx);
cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
using (var cn = new SqlConnection(cs))
using (var cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.AddWithValue("@idx", idx);
cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
cn.Open();
var result = cmd.ExecuteNonQuery();
cn.Close();
cmd.Dispose();
cn.Dispose();
cn.Open();
var result = cmd.ExecuteNonQuery();
return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "삭제되었습니다." : "삭제에 실패했습니다." });
}
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
}
}
return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "삭제되었습니다." : "삭제에 실패했습니다." });
/// <summary>
/// 업무형태 목록 조회 (Common 테이블, grp='15')
/// 트리뷰 형태: process > jobgrp > type
/// </summary>
public string Jobreport_GetJobTypes(string process = "")
{
try
{
var sql = @"SELECT idx, code, memo as [type], svalue as jobgrp, svalue2 as process
FROM Common WITH (nolock)
WHERE gcode = @gcode AND grp = '15'";
var parameters = new List<SqlParameter>();
parameters.Add(new SqlParameter("@gcode", info.Login.gcode));
if (!string.IsNullOrEmpty(process))
{
sql += " AND svalue2 = @process";
parameters.Add(new SqlParameter("@process", process));
}
sql += " ORDER BY svalue2, svalue, memo";
var cs = Properties.Settings.Default.gwcs;
using (var cn = new SqlConnection(cs))
using (var cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.AddRange(parameters.ToArray());
using (var da = new SqlDataAdapter(cmd))
{
var dt = new DataTable();
da.Fill(dt);
return JsonConvert.SerializeObject(new { Success = true, Data = dt });
}
}
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message });
}
}
/// <summary>
/// 업무일지 권한 정보 조회
/// 본인이거나 권한 레벨 5 이상이면 OT 열을 볼 수 있음
/// </summary>
public string Jobreport_GetPermission(string targetUserId)
{
try
{
int curLevel = Math.Max(info.Login.level, DBM.getAuth(DBM.eAuthType.jobreport));
bool canViewOT = string.IsNullOrEmpty(targetUserId) ||
targetUserId == info.Login.no ||
curLevel >= 5;
return JsonConvert.SerializeObject(new
{
Success = true,
CurrentUserId = info.Login.no,
Level = curLevel,
CanViewOT = canViewOT
});
}
catch (Exception ex)
{