feat: 품목정보 상세 패널 추가 및 프로젝트/근태/권한 기능 확장

- Items: 우측에 이미지, 담당자, 입고/발주내역 패널 추가 (fItems 윈폼 동일)
- Project: 목록 및 상세 다이얼로그 구현
- Kuntae: 오류검사/수정 기능 추가
- UserAuth: 사용자 권한 관리 페이지 추가
- UserGroup: 그룹정보 다이얼로그로 전환
- Header: 사용자 메뉴 서브메뉴 방향 수정, 즐겨찾기 기능
- Backend API: Items 상세/담당자/구매내역, 근태 오류검사, 프로젝트 목록 등

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
backuppc
2025-11-28 17:36:20 +09:00
parent c9b5d756e1
commit adcdc40169
32 changed files with 6668 additions and 292 deletions

View File

@@ -228,6 +228,50 @@ namespace Project.Web
}
}
/// <summary>
/// 즐겨찾기 목록 조회 (grp=17)
/// memo가 표시명, svalue가 URL
/// </summary>
public string Favorite_GetList()
{
try
{
var sql = "select isnull(code,'') as code, isnull(memo,'') as name, isnull(svalue,'') as url " +
"from common WITH (nolock) " +
"where gcode = @gcode and grp = '17' and isnull(code,'') <> '' " +
"order by code";
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();
var result = new System.Collections.Generic.List<object>();
foreach (DataRow dr in dt.Rows)
{
result.Add(new
{
name = dr["name"]?.ToString() ?? "",
url = dr["url"]?.ToString() ?? ""
});
}
return JsonConvert.SerializeObject(new { Success = true, Data = result });
}
catch (Exception ex)
{
Console.WriteLine($"Favorite_GetList 오류: {ex.Message}");
return JsonConvert.SerializeObject(new { Success = false, Data = new object[] { }, Message = ex.Message });
}
}
#endregion
#region Items API
@@ -421,6 +465,349 @@ namespace Project.Web
}
}
/// <summary>
/// 품목 이미지 조회 (Base64 반환)
/// </summary>
public string Items_GetImage(int idx)
{
try
{
var cs = Properties.Settings.Default.gwcs;
using (var cn = new SqlConnection(cs))
using (var cmd = new SqlCommand("SELECT image FROM Items WHERE idx = @idx AND gcode = @gcode", cn))
{
cmd.Parameters.AddWithValue("@idx", idx);
cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
cn.Open();
var data = cmd.ExecuteScalar() as byte[];
if (data != null && data.Length > 0)
{
var base64 = Convert.ToBase64String(data);
return JsonConvert.SerializeObject(new { Success = true, Data = base64 });
}
return JsonConvert.SerializeObject(new { Success = true, Data = (string)null });
}
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { Success = false, Message = "이미지 조회 실패: " + ex.Message });
}
}
/// <summary>
/// 품목 이미지 저장 (Base64 입력)
/// </summary>
public string Items_SaveImage(int idx, string base64Image)
{
try
{
byte[] imageData = null;
if (!string.IsNullOrEmpty(base64Image))
{
// data:image/png;base64, 형식 제거
if (base64Image.Contains(","))
{
base64Image = base64Image.Substring(base64Image.IndexOf(",") + 1);
}
imageData = Convert.FromBase64String(base64Image);
// 이미지 크기 조정 (640x480 제한, WinForms과 동일)
using (var ms = new System.IO.MemoryStream(imageData))
using (var img = System.Drawing.Image.FromStream(ms))
{
System.Drawing.Image resized = img;
bool needResize = false;
if (img.Width > 640)
{
var newRate = 640.0 / img.Width;
var newHeight = (int)(img.Height * newRate);
resized = new System.Drawing.Bitmap(640, newHeight);
using (var g = System.Drawing.Graphics.FromImage(resized))
{
g.DrawImage(img, new System.Drawing.Rectangle(0, 0, 640, newHeight));
}
needResize = true;
}
else if (img.Height > 480)
{
var newRate = 480.0 / img.Height;
var newWidth = (int)(img.Width * newRate);
resized = new System.Drawing.Bitmap(newWidth, 480);
using (var g = System.Drawing.Graphics.FromImage(resized))
{
g.DrawImage(img, new System.Drawing.Rectangle(0, 0, newWidth, 480));
}
needResize = true;
}
using (var outMs = new System.IO.MemoryStream())
{
resized.Save(outMs, System.Drawing.Imaging.ImageFormat.Jpeg);
imageData = outMs.ToArray();
}
if (needResize)
{
resized.Dispose();
}
}
}
var cs = Properties.Settings.Default.gwcs;
using (var cn = new SqlConnection(cs))
using (var cmd = new SqlCommand("UPDATE Items SET image = @image, wuid = @wuid, wdate = GETDATE() WHERE idx = @idx AND gcode = @gcode", cn))
{
cmd.Parameters.AddWithValue("@idx", idx);
cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
cmd.Parameters.AddWithValue("@wuid", info.Login.no);
if (imageData != null)
{
cmd.Parameters.Add("@image", SqlDbType.VarBinary, -1).Value = imageData;
}
else
{
cmd.Parameters.Add("@image", SqlDbType.VarBinary, -1).Value = DBNull.Value;
}
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 });
}
}
/// <summary>
/// 품목 이미지 삭제
/// </summary>
public string Items_DeleteImage(int idx)
{
return Items_SaveImage(idx, null);
}
/// <summary>
/// 품목의 공급처 담당자 조회
/// </summary>
public string Items_GetSupplierStaff(int supplyIdx)
{
try
{
if (supplyIdx <= 0)
{
return JsonConvert.SerializeObject(new { Success = true, Data = new object[] { } });
}
var sql = @"SELECT idx, name, grade, dept, tel, email, memo
FROM Staff WITH (NOLOCK)
WHERE gcode = @gcode AND cid = @cid";
var cs = Properties.Settings.Default.gwcs;
using (var cn = new SqlConnection(cs))
using (var cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.AddWithValue("@gcode", info.Login.gcode);
cmd.Parameters.AddWithValue("@cid", supplyIdx);
var da = new SqlDataAdapter(cmd);
var dt = new DataTable();
da.Fill(dt);
var result = new System.Collections.Generic.List<object>();
foreach (DataRow dr in dt.Rows)
{
var name = dr["name"]?.ToString() ?? "";
var grade = dr["grade"]?.ToString() ?? "";
if (!string.IsNullOrEmpty(grade))
{
name += $"({grade})";
}
result.Add(new
{
idx = Convert.ToInt32(dr["idx"]),
name = name,
tel = dr["tel"]?.ToString() ?? "",
email = dr["email"]?.ToString() ?? "",
dept = dr["dept"]?.ToString() ?? ""
});
}
return JsonConvert.SerializeObject(new { Success = true, Data = result });
}
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { Success = false, Message = "담당자 조회 실패: " + ex.Message });
}
}
/// <summary>
/// 품목의 최근 입고내역 조회 (indate 기준)
/// </summary>
public string Items_GetIncomingHistory(int itemIdx)
{
try
{
var sql = @"SELECT TOP 10 idx, indate, request, pumqty, pumprice, state
FROM Purchase WITH (NOLOCK)
WHERE pumidx = @pumidx
AND ISNULL(indate, '') <> ''
AND ISNULL(isdel, 0) = 0
ORDER BY indate DESC";
var cs = Properties.Settings.Default.gwcs;
using (var cn = new SqlConnection(cs))
using (var cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.AddWithValue("@pumidx", itemIdx);
var da = new SqlDataAdapter(cmd);
var dt = new DataTable();
da.Fill(dt);
var result = new System.Collections.Generic.List<object>();
foreach (DataRow dr in dt.Rows)
{
var date = dr["indate"]?.ToString() ?? "";
// 년도 2자리로 표시 (2024-01-01 -> 24-01-01)
if (date.Length > 9) date = date.Substring(2);
result.Add(new
{
idx = Convert.ToInt32(dr["idx"]),
date = date,
request = dr["request"]?.ToString() ?? "",
qty = dr["pumqty"] == DBNull.Value ? 0 : Convert.ToInt32(dr["pumqty"]),
price = dr["pumprice"] == DBNull.Value ? 0m : Convert.ToDecimal(dr["pumprice"]),
state = dr["state"]?.ToString() ?? ""
});
}
return JsonConvert.SerializeObject(new { Success = true, Data = result });
}
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { Success = false, Message = "입고내역 조회 실패: " + ex.Message });
}
}
/// <summary>
/// 품목의 발주내역 조회 (pdate 기준)
/// </summary>
public string Items_GetOrderHistory(int itemIdx)
{
try
{
var sql = @"SELECT TOP 10 idx, pdate, request, pumqty, pumprice, state
FROM Purchase WITH (NOLOCK)
WHERE pumidx = @pumidx
AND state <> 'Cancled'
AND ISNULL(isdel, 0) = 0
ORDER BY pdate DESC";
var cs = Properties.Settings.Default.gwcs;
using (var cn = new SqlConnection(cs))
using (var cmd = new SqlCommand(sql, cn))
{
cmd.Parameters.AddWithValue("@pumidx", itemIdx);
var da = new SqlDataAdapter(cmd);
var dt = new DataTable();
da.Fill(dt);
var result = new System.Collections.Generic.List<object>();
foreach (DataRow dr in dt.Rows)
{
var date = dr["pdate"]?.ToString() ?? "";
// 년도 2자리로 표시 (2024-01-01 -> 24-01-01)
if (date.Length > 9) date = date.Substring(2);
result.Add(new
{
idx = Convert.ToInt32(dr["idx"]),
date = date,
request = dr["request"]?.ToString() ?? "",
qty = dr["pumqty"] == DBNull.Value ? 0 : Convert.ToInt32(dr["pumqty"]),
price = dr["pumprice"] == DBNull.Value ? 0m : Convert.ToDecimal(dr["pumprice"]),
state = dr["state"]?.ToString() ?? ""
});
}
return JsonConvert.SerializeObject(new { Success = true, Data = result });
}
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { Success = false, Message = "발주내역 조회 실패: " + ex.Message });
}
}
/// <summary>
/// 품목 상세 정보 조회 (supplyidx 포함)
/// </summary>
public string Items_GetDetail(int idx)
{
try
{
var sql = @"SELECT idx, sid, cate, name, model, scale, unit, price, supply, supplyidx, manu, storage, disable, memo
FROM Items WITH (NOLOCK)
WHERE idx = @idx AND gcode = @gcode";
var cs = Properties.Settings.Default.gwcs;
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();
using (var reader = cmd.ExecuteReader())
{
if (reader.Read())
{
var item = new
{
idx = reader.GetInt32(reader.GetOrdinal("idx")),
sid = reader["sid"]?.ToString() ?? "",
cate = reader["cate"]?.ToString() ?? "",
name = reader["name"]?.ToString() ?? "",
model = reader["model"]?.ToString() ?? "",
scale = reader["scale"]?.ToString() ?? "",
unit = reader["unit"]?.ToString() ?? "",
price = reader["price"] == DBNull.Value ? 0m : Convert.ToDecimal(reader["price"]),
supply = reader["supply"]?.ToString() ?? "",
supplyidx = reader["supplyidx"] == DBNull.Value ? -1 : Convert.ToInt32(reader["supplyidx"]),
manu = reader["manu"]?.ToString() ?? "",
storage = reader["storage"]?.ToString() ?? "",
disable = reader["disable"] != DBNull.Value && Convert.ToBoolean(reader["disable"]),
memo = reader["memo"]?.ToString() ?? ""
};
return JsonConvert.SerializeObject(new { Success = true, Data = item });
}
else
{
return JsonConvert.SerializeObject(new { Success = false, Message = "품목을 찾을 수 없습니다." });
}
}
}
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { Success = false, Message = "품목 상세 조회 실패: " + ex.Message });
}
}
#endregion
}
}