using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; namespace Project.Web { /// /// GroupWare WebSocket 서버 /// npm run dev 환경에서 핫 리로드 개발을 위한 WebSocket 통신 지원 /// public class WebSocketServer { private HttpListener _httpListener; private List _clients = new List(); private ConcurrentDictionary _socketLocks = new ConcurrentDictionary(); private MachineBridge _bridge; private bool _isRunning = false; public WebSocketServer(string url, MachineBridge bridge) { _bridge = bridge; _httpListener = new HttpListener(); _httpListener.Prefixes.Add(url); } public void Start() { if (_isRunning) return; try { _httpListener.Start(); _isRunning = true; Console.WriteLine($"[WS] GroupWare WebSocket Server Started"); Task.Run(AcceptConnections); } catch (Exception ex) { Console.WriteLine($"[WS] Start Error: {ex.Message}"); } } public void Stop() { _isRunning = false; try { _httpListener.Stop(); _httpListener.Close(); } catch { } } private async Task AcceptConnections() { while (_httpListener.IsListening && _isRunning) { try { var context = await _httpListener.GetContextAsync(); if (context.Request.IsWebSocketRequest) { ProcessRequest(context); } else { context.Response.StatusCode = 400; context.Response.Close(); } } catch (Exception ex) { if (_isRunning) Console.WriteLine($"[WS] Accept Error: {ex.Message}"); } } } private async void ProcessRequest(HttpListenerContext context) { WebSocketContext wsContext = null; try { wsContext = await context.AcceptWebSocketAsync(subProtocol: null); WebSocket socket = wsContext.WebSocket; _socketLocks.TryAdd(socket, new SemaphoreSlim(1, 1)); lock (_clients) { _clients.Add(socket); } Console.WriteLine("[WS] Client Connected"); await ReceiveLoop(socket); } catch (Exception ex) { Console.WriteLine($"[WS] Process Error: {ex.Message}"); } finally { if (wsContext != null) { WebSocket socket = wsContext.WebSocket; lock (_clients) { _clients.Remove(socket); } if (_socketLocks.TryRemove(socket, out var semaphore)) { semaphore.Dispose(); } socket.Dispose(); } } } private async Task ReceiveLoop(WebSocket socket) { var buffer = new byte[1024 * 4]; while (socket.State == WebSocketState.Open && _isRunning) { try { var result = await socket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); if (result.MessageType == WebSocketMessageType.Close) { await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None); } else if (result.MessageType == WebSocketMessageType.Text) { string msg = Encoding.UTF8.GetString(buffer, 0, result.Count); await HandleMessage(msg, socket); } } catch { break; } } } private async Task HandleMessage(string msg, WebSocket socket) { try { dynamic json = JsonConvert.DeserializeObject(msg); string type = json.type; Console.WriteLine($"[WS] Message: {type}"); switch (type) { // ===== Todo API ===== case "GET_TODOS": { string result = _bridge.Todo_GetTodos(); var response = new { type = "TODOS_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "GET_TODO": { int id = json.id; string result = _bridge.GetTodo(id); var response = new { type = "TODO_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "CREATE_TODO": { string title = json.title ?? ""; string remark = json.remark ?? ""; string expire = json.expire; int seqno = json.seqno ?? 0; bool flag = json.flag ?? false; string request = json.request; string status = json.status ?? "0"; string result = _bridge.CreateTodo(title, remark, expire, seqno, flag, request, status); var response = new { type = "TODO_CREATED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "UPDATE_TODO": { int idx = json.idx; string title = json.title ?? ""; string remark = json.remark ?? ""; string expire = json.expire; int seqno = json.seqno ?? 0; bool flag = json.flag ?? false; string request = json.request; string status = json.status ?? "0"; string result = _bridge.Todo_UpdateTodo(idx, title, remark, expire, seqno, flag, request, status); var response = new { type = "TODO_UPDATED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "DELETE_TODO": { int id = json.id; string result = _bridge.Todo_DeleteTodo(id); var response = new { type = "TODO_DELETED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "GET_URGENT_TODOS": { string result = _bridge.GetUrgentTodos(); var response = new { type = "URGENT_TODOS_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== Dashboard API ===== case "GET_PURCHASE_WAIT_COUNT": { string result = _bridge.GetPurchaseWaitCount(); var response = new { type = "PURCHASE_WAIT_COUNT_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "GET_TODAY_COUNT_H": { string result = _bridge.TodayCountH(); var response = new { type = "TODAY_COUNT_H_DATA", count = int.Parse(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "GET_HOLY_USER": { string result = _bridge.GetHolyUser(); var response = new { type = "HOLY_USER_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "GET_HOLY_REQUEST_USER": { string result = _bridge.GetHolyRequestUser(); var response = new { type = "HOLY_REQUEST_USER_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "GET_PURCHASE_NR_LIST": { string result = _bridge.GetPurchaseNRList(); var response = new { type = "PURCHASE_NR_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "GET_PURCHASE_CR_LIST": { string result = _bridge.GetPurchaseCRList(); var response = new { type = "PURCHASE_CR_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "GET_HOLYDAY_REQUEST_COUNT": { string result = _bridge.GetHolydayRequestCount(); var response = new { type = "HOLYDAY_REQUEST_COUNT_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "GET_CURRENT_USER_COUNT": { string result = _bridge.GetCurrentUserCount(); var response = new { type = "CURRENT_USER_COUNT_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== Login API ===== case "CHECK_LOGIN_STATUS": { string result = _bridge.CheckLoginStatus(); var response = new { type = "LOGIN_STATUS_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "LOGIN": { string gcode = json.gcode ?? ""; string id = json.id ?? ""; string password = json.password ?? ""; bool rememberMe = json.rememberMe ?? false; string result = _bridge.Login(gcode, id, password, rememberMe); var response = new { type = "LOGIN_RESULT", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "LOGOUT": { string result = _bridge.Logout(); var response = new { type = "LOGOUT_RESULT", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "GET_USER_GROUPS": { string result = _bridge.GetUserGroups(); var response = new { type = "USER_GROUPS_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "GET_PREVIOUS_LOGIN_INFO": { string result = _bridge.GetPreviousLoginInfo(); var response = new { type = "PREVIOUS_LOGIN_INFO_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== User API ===== case "GET_CURRENT_USER_INFO": { string result = _bridge.GetCurrentUserInfo(); var response = new { type = "CURRENT_USER_INFO_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "GET_USER_INFO_BY_ID": { string userId = json.userId ?? ""; string result = _bridge.GetUserInfoById(userId); var response = new { type = "USER_INFO_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "SAVE_USER_INFO": { string userData = JsonConvert.SerializeObject(json.userData); string result = _bridge.SaveUserInfo(userData); var response = new { type = "USER_INFO_SAVED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "CHANGE_PASSWORD": { string oldPassword = json.oldPassword ?? ""; string newPassword = json.newPassword ?? ""; string result = _bridge.ChangePassword(oldPassword, newPassword); var response = new { type = "PASSWORD_CHANGED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== Common Code API ===== case "COMMON_GET_GROUPS": { string result = _bridge.Common_GetGroups(); var response = new { type = "COMMON_GROUPS_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "COMMON_GET_LIST": { string grp = json.grp ?? "99"; string result = _bridge.Common_GetList(grp); var response = new { type = "COMMON_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "COMMON_SAVE": { int idx = json.idx ?? 0; string grp = json.grp ?? ""; string code = json.code ?? ""; string svalue = json.svalue ?? ""; int ivalue = json.ivalue ?? 0; float fvalue = json.fvalue ?? 0f; string svalue2 = json.svalue2 ?? ""; string memo = json.memo ?? ""; string result = _bridge.Common_Save(idx, grp, code, svalue, ivalue, fvalue, svalue2, memo); var response = new { type = "COMMON_SAVED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "COMMON_DELETE": { int idx = json.idx ?? 0; string result = _bridge.Common_Delete(idx); var response = new { type = "COMMON_DELETED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "FAVORITE_GET_LIST": { string result = _bridge.Favorite_GetList(); var response = new { type = "FAVORITE_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== Items API ===== case "ITEMS_GET_CATEGORIES": { string result = _bridge.Items_GetCategories(); var response = new { type = "ITEMS_CATEGORIES_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "ITEMS_GET_LIST": { string category = json.category ?? ""; string searchKey = json.searchKey ?? ""; string result = _bridge.Items_GetList(category, searchKey); var response = new { type = "ITEMS_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "ITEMS_SAVE": { int idx = json.idx ?? 0; string sid = json.sid ?? ""; string cate = json.cate ?? ""; string name = json.name ?? ""; string model = json.model ?? ""; string scale = json.scale ?? ""; string unit = json.unit ?? ""; decimal price = json.price ?? 0m; string supply = json.supply ?? ""; string manu = json.manu ?? ""; string storage = json.storage ?? ""; bool disable = json.disable ?? false; string memo = json.memo ?? ""; string result = _bridge.Items_Save(idx, sid, cate, name, model, scale, unit, price, supply, manu, storage, disable, memo); var response = new { type = "ITEMS_SAVED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "ITEMS_DELETE": { int idx = json.idx ?? 0; string result = _bridge.Items_Delete(idx); var response = new { type = "ITEMS_DELETED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "ITEMS_GET_IMAGE": { int idx = json.idx ?? 0; string result = _bridge.Items_GetImage(idx); var response = new { type = "ITEMS_IMAGE_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "ITEMS_SAVE_IMAGE": { int idx = json.idx ?? 0; string base64Image = json.base64Image ?? ""; string result = _bridge.Items_SaveImage(idx, base64Image); var response = new { type = "ITEMS_IMAGE_SAVED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "ITEMS_DELETE_IMAGE": { int idx = json.idx ?? 0; string result = _bridge.Items_DeleteImage(idx); var response = new { type = "ITEMS_IMAGE_DELETED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "ITEMS_GET_DETAIL": { int idx = json.idx ?? 0; string result = _bridge.Items_GetDetail(idx); var response = new { type = "ITEMS_DETAIL_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "ITEMS_GET_SUPPLIER_STAFF": { int supplyIdx = json.supplyIdx ?? 0; string result = _bridge.Items_GetSupplierStaff(supplyIdx); var response = new { type = "ITEMS_SUPPLIER_STAFF_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "ITEMS_GET_INCOMING_HISTORY": { int itemIdx = json.itemIdx ?? 0; string result = _bridge.Items_GetIncomingHistory(itemIdx); var response = new { type = "ITEMS_INCOMING_HISTORY_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "ITEMS_GET_ORDER_HISTORY": { int itemIdx = json.itemIdx ?? 0; string result = _bridge.Items_GetOrderHistory(itemIdx); var response = new { type = "ITEMS_ORDER_HISTORY_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== UserList API ===== case "USERLIST_GET_CURRENT_LEVEL": { string result = _bridge.UserList_GetCurrentLevel(); var response = new { type = "USERLIST_CURRENT_LEVEL_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "USERLIST_GET_DEPTS": { string result = _bridge.UserList_GetDepts(); var response = new { type = "USERLIST_DEPTS_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "USERLIST_GET_LIST": { string process = json.process ?? "%"; string result = _bridge.UserList_GetList(process); var response = new { type = "USERLIST_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "USERLIST_GET_USER": { string userId = json.userId ?? ""; string result = _bridge.UserList_GetUser(userId); var response = new { type = "USERLIST_USER_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "USERLIST_SAVE_GROUP_USER": { string userId = json.userId ?? ""; string dept = json.dept ?? ""; int level = json.level ?? 1; bool useUserState = json.useUserState ?? false; bool useJobReport = json.useJobReport ?? false; bool exceptHoly = json.exceptHoly ?? false; string result = _bridge.UserList_SaveGroupUser(userId, dept, level, useUserState, useJobReport, exceptHoly); var response = new { type = "USERLIST_SAVED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "USERLIST_SAVE_USER_FULL": { string userData = JsonConvert.SerializeObject(json.userData); string result = _bridge.UserList_SaveUserFull(userData); var response = new { type = "USERLIST_USER_FULL_SAVED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "USERLIST_DELETE_GROUP_USER": { string userId = json.userId ?? ""; string result = _bridge.UserList_DeleteGroupUser(userId); var response = new { type = "USERLIST_DELETED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== JobReport API (JobReport 뷰/테이블) ===== case "JOBREPORT_GET_LIST": { string sd = json.sd ?? ""; string ed = json.ed ?? ""; string uid = json.uid ?? ""; string cate = json.cate ?? ""; // 사용안함 (호환성) string searchKey = json.searchKey ?? ""; Console.WriteLine($"[WS] JOBREPORT_GET_LIST: sd={sd}, ed={ed}, uid={uid}, searchKey={searchKey}"); string result = _bridge.Jobreport_GetList(sd, ed, uid, cate, searchKey); var response = new { type = "JOBREPORT_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "JOBREPORT_GET_USERS": { string result = _bridge.Jobreport_GetUsers(); var response = new { type = "JOBREPORT_USERS_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "JOBREPORT_GET_DETAIL": { int idx = json.idx ?? 0; string result = _bridge.Jobreport_GetDetail(idx); var response = new { type = "JOBREPORT_DETAIL_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "JOBREPORT_ADD": { string pdate = json.pdate ?? ""; string projectName = json.projectName ?? ""; int pidx = json.pidx ?? -1; string requestpart = json.requestpart ?? ""; string package = json.package ?? ""; string jobType = json.jobType ?? ""; // type -> jobType (WebSocket type 필드 충돌 방지) string process = json.process ?? ""; string status = json.status ?? "진행 완료"; string description = json.description ?? ""; double hrs = json.hrs ?? 0.0; double ot = json.ot ?? 0.0; string jobgrp = json.jobgrp ?? ""; string tag = json.tag ?? ""; string otStart = json.otStart ?? "00:00"; string otEnd = json.otEnd ?? "00:00"; string result = _bridge.Jobreport_Add(pdate, projectName, pidx, requestpart, package, jobType, process, status, description, hrs, ot, jobgrp, tag, otStart, otEnd); var response = new { type = "JOBREPORT_ADDED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "JOBREPORT_EDIT": { int idx = json.idx ?? 0; string pdate = json.pdate ?? ""; string projectName = json.projectName ?? ""; int pidx = json.pidx ?? -1; string requestpart = json.requestpart ?? ""; string package = json.package ?? ""; string jobType = json.jobType ?? ""; // type -> jobType (WebSocket type 필드 충돌 방지) string process = json.process ?? ""; string status = json.status ?? ""; string description = json.description ?? ""; double hrs = json.hrs ?? 0.0; double ot = json.ot ?? 0.0; string jobgrp = json.jobgrp ?? ""; string tag = json.tag ?? ""; string otStart = json.otStart ?? "00:00"; string otEnd = json.otEnd ?? "00:00"; string result = _bridge.Jobreport_Edit(idx, pdate, projectName, pidx, requestpart, package, jobType, process, status, description, hrs, ot, jobgrp, tag, otStart, otEnd); var response = new { type = "JOBREPORT_EDITED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "JOBREPORT_DELETE": { int idx = json.idx ?? 0; string result = _bridge.Jobreport_Delete(idx); var response = new { type = "JOBREPORT_DELETED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "JOBREPORT_GET_PERMISSION": { string targetUserId = json.targetUserId ?? ""; string result = _bridge.Jobreport_GetPermission(targetUserId); var response = new { type = "JOBREPORT_PERMISSION", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "JOBREPORT_GET_LAST_BY_PROJECT": { int pidx = json.pidx ?? -1; string projectName = json.projectName ?? ""; string result = _bridge.Jobreport_GetLastByProject(pidx, projectName); var response = new { type = "JOBREPORT_LAST_BY_PROJECT", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "JOBREPORT_GET_JOBTYPES": { string process = json.process ?? ""; string result = _bridge.Jobreport_GetJobTypes(process); var response = new { type = "JOBREPORT_JOBTYPES", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "GET_APP_VERSION": { string result = _bridge.GetAppVersion(); var response = new { type = "APP_VERSION", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== Kuntae API ===== case "GET_KUNTAE_LIST": { string sd = json.sd ?? ""; string ed = json.ed ?? ""; string result = _bridge.Kuntae_GetList(sd, ed); var response = new { type = "KUNTAE_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "DELETE_KUNTAE": { int id = json.id ?? 0; string result = _bridge.Kuntae_Delete(id); var response = new { type = "KUNTAE_DELETED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== Holyday (근태) API ===== case "HOLYDAY_GET_LIST": { string sd = json.sd ?? ""; string ed = json.ed ?? ""; string uid = json.uid ?? "%"; string result = _bridge.Holyday_GetList(sd, ed, uid); var response = new { type = "HOLYDAY_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "HOLYDAY_GET_DETAIL": { int idx = json.idx ?? 0; string result = _bridge.Holyday_GetDetail(idx); var response = new { type = "HOLYDAY_DETAIL_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "HOLYDAY_ADD": { string cate = json.cate ?? ""; string sdate = json.sdate ?? ""; string edate = json.edate ?? ""; double term = json.term ?? 0.0; double crtime = json.crtime ?? 0.0; double termDr = json.termDr ?? 0.0; double drTime = json.drTime ?? 0.0; string contents = json.contents ?? ""; string uid = json.uid ?? ""; string result = _bridge.Holyday_Add(cate, sdate, edate, term, crtime, termDr, drTime, contents, uid); var response = new { type = "HOLYDAY_ADDED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "HOLYDAY_EDIT": { int idx = json.idx ?? 0; string cate = json.cate ?? ""; string sdate = json.sdate ?? ""; string edate = json.edate ?? ""; double term = json.term ?? 0.0; double crtime = json.crtime ?? 0.0; double termDr = json.termDr ?? 0.0; double drTime = json.drTime ?? 0.0; string contents = json.contents ?? ""; string result = _bridge.Holyday_Edit(idx, cate, sdate, edate, term, crtime, termDr, drTime, contents); var response = new { type = "HOLYDAY_EDITED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "HOLYDAY_DELETE": { int idx = json.idx ?? 0; string result = _bridge.Holyday_Delete(idx); var response = new { type = "HOLYDAY_DELETED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "HOLYDAY_GET_USERLIST": { string sd = json.sd ?? ""; string ed = json.ed ?? ""; string result = _bridge.Holyday_GetUserList(sd, ed); var response = new { type = "HOLYDAY_USERLIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "HOLYDAY_GET_PERMISSION": { string result = _bridge.Holyday_GetPermission(); var response = new { type = "HOLYDAY_PERMISSION_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "HOLYDAY_GET_BALANCE": { string year = json.year ?? ""; string uid = json.uid ?? ""; string result = _bridge.Holyday_GetBalance(year, uid); var response = new { type = "HOLYDAY_BALANCE_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== Note API (메모장) ===== case "NOTE_GET_LIST": { string startDate = json.startDate ?? ""; string endDate = json.endDate ?? ""; string uid = json.uid ?? ""; string result = _bridge.Note_GetList(startDate, endDate, uid); var response = new { type = "NOTE_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "NOTE_GET_DETAIL": { int idx = json.idx ?? 0; string result = _bridge.Note_GetDetail(idx); var response = new { type = "NOTE_DETAIL_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "NOTE_ADD": { string pdate = json.pdate ?? ""; string title = json.title ?? ""; string uid = json.uid ?? ""; string description = json.description ?? ""; string description2 = json.description2 ?? ""; bool share = json.share ?? false; string guid = json.guid ?? ""; string result = _bridge.Note_Add(pdate, title, uid, description, description2, share, guid); var response = new { type = "NOTE_ADDED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "NOTE_EDIT": { int idx = json.idx ?? 0; string pdate = json.pdate ?? ""; string title = json.title ?? ""; string uid = json.uid ?? ""; string description = json.description ?? ""; string description2 = json.description2 ?? ""; bool share = json.share ?? false; string guid = json.guid ?? ""; string result = _bridge.Note_Edit(idx, pdate, title, uid, description, description2, share, guid); var response = new { type = "NOTE_EDITED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "NOTE_DELETE": { int idx = json.idx ?? 0; string result = _bridge.Note_Delete(idx); var response = new { type = "NOTE_DELETED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== Board API (게시판 - 패치내역 등) ===== case "BOARD_GET_LIST": { int bidx = json.bidx ?? 5; string searchKey = json.searchKey ?? ""; string result = _bridge.Board_GetList(bidx, searchKey); var response = new { type = "BOARD_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "BOARD_GET_DETAIL": { int idx = json.idx ?? 0; string result = _bridge.Board_GetDetail(idx); var response = new { type = "BOARD_DETAIL_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "BOARD_ADD": { int bidx = json.bidx ?? 5; string header = json.header ?? ""; string cate = json.cate ?? ""; string title = json.title ?? ""; string contents = json.contents ?? ""; string result = _bridge.Board_Add(bidx, header, cate, title, contents); var response = new { type = "BOARD_ADDED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "BOARD_EDIT": { int idx = json.idx ?? 0; string header = json.header ?? ""; string cate = json.cate ?? ""; string title = json.title ?? ""; string contents = json.contents ?? ""; string result = _bridge.Board_Edit(idx, header, cate, title, contents); var response = new { type = "BOARD_EDITED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "BOARD_DELETE": { int idx = json.idx ?? 0; string result = _bridge.Board_Delete(idx); var response = new { type = "BOARD_DELETED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "BOARD_GET_REPLIES": { int rootIdx = json.rootIdx ?? 0; string result = _bridge.Board_GetReplies(rootIdx); var response = new { type = "BOARD_REPLIES_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "BOARD_ADD_REPLY": { int rootIdx = json.rootIdx ?? 0; int pidx = json.pidx ?? 0; string title = json.title ?? ""; string contents = json.contents ?? ""; bool isComment = json.isComment ?? false; string result = _bridge.Board_AddReply(rootIdx, pidx, title, contents, isComment); var response = new { type = "BOARD_REPLY_ADDED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== Mail API (메일 발신 내역) ===== case "MAIL_GET_LIST": { string startDate = json.startDate ?? ""; string endDate = json.endDate ?? ""; string searchKey = json.searchKey ?? ""; string result = _bridge.Mail_GetList(startDate, endDate, searchKey); var response = new { type = "MAIL_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "MAIL_ADD_DATA": { string cate = json.cate ?? ""; string subject = json.subject ?? ""; string fromlist = json.fromlist ?? ""; string tolist = json.tolist ?? ""; string cc = json.cc ?? ""; string bcc = json.bcc ?? ""; string body = json.body ?? ""; string result = _bridge.Mail_AddData(cate, subject, fromlist, tolist, cc, bcc, body); var response = new { type = "MAIL_ADD_DATA_RESULT", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "MAIL_SEND_DIRECT": { string cate = json.cate ?? ""; string subject = json.subject ?? ""; string fromlist = json.fromlist ?? ""; string tolist = json.tolist ?? ""; string cc = json.cc ?? ""; string bcc = json.bcc ?? ""; string body = json.body ?? ""; string result = _bridge.Mail_SendDirect(cate, subject, fromlist, tolist, cc, bcc, body); var response = new { type = "MAIL_SEND_DIRECT_RESULT", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "MAIL_SEND_OUTLOOK": { string subject = json.subject ?? ""; string tolist = json.tolist ?? ""; string cc = json.cc ?? ""; string bcc = json.bcc ?? ""; string body = json.body ?? ""; string result = _bridge.Mail_SendOutlook(subject, tolist, cc, bcc, body); var response = new { type = "MAIL_SEND_OUTLOOK_RESULT", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== License API (라이선스 관리) ===== case "LICENSE_GET_LIST": { string result = _bridge.License_GetList(); var response = new { type = "LICENSE_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "LICENSE_ADD": { string name = json.name ?? ""; string version = json.version ?? ""; string meterialNo = json.meterialNo ?? ""; string supply = json.supply ?? ""; int qty = json.qty ?? 0; string uids = json.uids ?? ""; string serialNo = json.serialNo ?? ""; string remark = json.remark ?? ""; string sdate = json.sdate ?? ""; string edate = json.edate ?? ""; string manu = json.manu ?? ""; bool expire = json.expire ?? false; string result = _bridge.License_Add(name, version, meterialNo, supply, qty, uids, serialNo, remark, sdate, edate, manu, expire); var response = new { type = "LICENSE_ADD_RESULT", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "LICENSE_UPDATE": { int idx = json.idx ?? 0; string name = json.name ?? ""; string version = json.version ?? ""; string meterialNo = json.meterialNo ?? ""; string supply = json.supply ?? ""; int qty = json.qty ?? 0; string uids = json.uids ?? ""; string serialNo = json.serialNo ?? ""; string remark = json.remark ?? ""; string sdate = json.sdate ?? ""; string edate = json.edate ?? ""; string manu = json.manu ?? ""; bool expire = json.expire ?? false; string result = _bridge.License_Update(idx, name, version, meterialNo, supply, qty, uids, serialNo, remark, sdate, edate, manu, expire); var response = new { type = "LICENSE_UPDATE_RESULT", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "LICENSE_DELETE": { int idx = json.idx ?? 0; string result = _bridge.License_Delete(idx); var response = new { type = "LICENSE_DELETE_RESULT", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "LICENSE_OPEN_FOLDER": { int idx = json.idx ?? 0; string result = _bridge.License_OpenFolder(idx); var response = new { type = "LICENSE_OPEN_FOLDER_RESULT", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "LICENSE_EXPORT_CSV": { string filePath = json.filePath ?? ""; string result = _bridge.License_ExportCSV(filePath); var response = new { type = "LICENSE_EXPORT_CSV_RESULT", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== PartList API (파트리스트) ===== case "PARTLIST_GET_LIST": { int projectIdx = json.projectIdx ?? 0; string result = _bridge.PartList_GetList(projectIdx); var response = new { type = "PARTLIST_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "PARTLIST_SAVE": { int idx = json.idx ?? 0; int projectIdx = json.projectIdx ?? 0; string itemgroup = json.itemgroup ?? ""; string itemname = json.itemname ?? ""; string item = json.item ?? ""; string itemmodel = json.itemmodel ?? ""; string itemscale = json.itemscale ?? ""; string itemunit = json.itemunit ?? ""; double qty = json.qty ?? 0.0; double price = json.price ?? 0.0; string itemsupply = json.itemsupply ?? ""; int itemsupplyidx = json.itemsupplyidx ?? 0; string itemmanu = json.itemmanu ?? ""; string itemsid = json.itemsid ?? ""; string option1 = json.option1 ?? ""; string remark = json.remark ?? ""; int no = json.no ?? 0; double qtybuy = json.qtybuy ?? 0.0; string result = _bridge.PartList_Save(idx, projectIdx, itemgroup, itemname, item, itemmodel, itemscale, itemunit, qty, price, itemsupply, itemsupplyidx, itemmanu, itemsid, option1, remark, no, qtybuy); var response = new { type = "PARTLIST_SAVE_RESULT", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "PARTLIST_DELETE": { int idx = json.idx ?? 0; string result = _bridge.PartList_Delete(idx); var response = new { type = "PARTLIST_DELETE_RESULT", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== Customs API (업체정보) ===== case "CUSTOMS_GET_LIST": { string searchKey = json.searchKey ?? ""; string result = _bridge.Customs_GetList(searchKey); var response = new { type = "CUSTOMS_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "CUSTOMS_GET_DETAIL": { int idx = json.idx ?? 0; string result = _bridge.Customs_GetDetail(idx); var response = new { type = "CUSTOMS_DETAIL_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== Holiday API (월별근무표) ===== case "HOLIDAY_GET_LIST": { string month = json.month ?? ""; string result = _bridge.Holiday_GetList(month); var response = new { type = "HOLIDAY_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "HOLIDAY_SAVE": { string month = json.month ?? ""; string holidaysJson = JsonConvert.SerializeObject(json.holidays); string result = _bridge.Holiday_Save(month, holidaysJson); var response = new { type = "HOLIDAY_SAVED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "HOLIDAY_INITIALIZE": { string month = json.month ?? ""; string result = _bridge.Holiday_Initialize(month); var response = new { type = "HOLIDAY_INITIALIZED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== MailForm API (메일양식) ===== case "MAILFORM_GET_LIST": { string result = _bridge.MailForm_GetList(); var response = new { type = "MAILFORM_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "MAILFORM_GET_DETAIL": { int idx = json.idx ?? 0; string result = _bridge.MailForm_GetDetail(idx); var response = new { type = "MAILFORM_DETAIL_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "MAILFORM_ADD": { string cate = json.cate ?? ""; string title = json.title ?? ""; string tolist = json.tolist ?? ""; string bcc = json.bcc ?? ""; string cc = json.cc ?? ""; string subject = json.subject ?? ""; string tail = json.tail ?? ""; string body = json.body ?? ""; bool selfTo = json.selfTo ?? false; bool selfCC = json.selfCC ?? false; bool selfBCC = json.selfBCC ?? false; string exceptmail = json.exceptmail ?? ""; string exceptmailcc = json.exceptmailcc ?? ""; string result = _bridge.MailForm_Add(cate, title, tolist, bcc, cc, subject, tail, body, selfTo, selfCC, selfBCC, exceptmail, exceptmailcc); var response = new { type = "MAILFORM_ADDED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "MAILFORM_EDIT": { int idx = json.idx ?? 0; string cate = json.cate ?? ""; string title = json.title ?? ""; string tolist = json.tolist ?? ""; string bcc = json.bcc ?? ""; string cc = json.cc ?? ""; string subject = json.subject ?? ""; string tail = json.tail ?? ""; string body = json.body ?? ""; bool selfTo = json.selfTo ?? false; bool selfCC = json.selfCC ?? false; bool selfBCC = json.selfBCC ?? false; string exceptmail = json.exceptmail ?? ""; string exceptmailcc = json.exceptmailcc ?? ""; string result = _bridge.MailForm_Edit(idx, cate, title, tolist, bcc, cc, subject, tail, body, selfTo, selfCC, selfBCC, exceptmail, exceptmailcc); var response = new { type = "MAILFORM_EDITED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "MAILFORM_DELETE": { int idx = json.idx ?? 0; string result = _bridge.MailForm_Delete(idx); var response = new { type = "MAILFORM_DELETED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== UserGroup API (그룹정보/권한설정) ===== case "USERGROUP_GET_LIST": { string result = _bridge.UserGroup_GetList(); var response = new { type = "USERGROUP_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "USERGROUP_ADD": { string dept = json.dept ?? ""; string path_kj = json.path_kj ?? ""; int permission = json.permission ?? 1; bool advpurchase = json.advpurchase ?? false; bool advkisul = json.advkisul ?? false; string managerinfo = json.managerinfo ?? ""; string devinfo = json.devinfo ?? ""; bool usemail = json.usemail ?? false; string result = _bridge.UserGroup_Add(dept, path_kj, permission, advpurchase, advkisul, managerinfo, devinfo, usemail); var response = new { type = "USERGROUP_ADDED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "USERGROUP_EDIT": { string originalDept = json.originalDept ?? ""; string dept = json.dept ?? ""; string path_kj = json.path_kj ?? ""; int permission = json.permission ?? 1; bool advpurchase = json.advpurchase ?? false; bool advkisul = json.advkisul ?? false; string managerinfo = json.managerinfo ?? ""; string devinfo = json.devinfo ?? ""; bool usemail = json.usemail ?? false; string result = _bridge.UserGroup_Edit(originalDept, dept, path_kj, permission, advpurchase, advkisul, managerinfo, devinfo, usemail); var response = new { type = "USERGROUP_EDITED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "USERGROUP_DELETE": { string dept = json.dept ?? ""; string result = _bridge.UserGroup_Delete(dept); var response = new { type = "USERGROUP_DELETED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "USERGROUP_GET_PERMISSION_INFO": { string result = _bridge.UserGroup_GetPermissionInfo(); var response = new { type = "USERGROUP_PERMISSION_INFO", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== UserAuth API (사용자 권한) ===== case "USERAUTH_CAN_ACCESS": { string result = _bridge.UserAuth_CanAccess(); var response = new { type = "USERAUTH_CAN_ACCESS_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "USERAUTH_GET_LIST": { string result = _bridge.UserAuth_GetList(); var response = new { type = "USERAUTH_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "USERAUTH_SAVE": { int idx = json.idx ?? 0; string user = json.user ?? ""; int account = json.account ?? 0; int purchase = json.purchase ?? 0; int purchaseEB = json.purchaseEB ?? 0; int holyday = json.holyday ?? 0; int project = json.project ?? 0; int jobreport = json.jobreport ?? 0; int scheapp = json.scheapp ?? 0; int equipment = json.equipment ?? 0; int otconfirm = json.otconfirm ?? 0; int holyreq = json.holyreq ?? 0; int kuntae = json.kuntae ?? 0; string result = _bridge.UserAuth_Save(idx, user, account, purchase, purchaseEB, holyday, project, jobreport, scheapp, equipment, otconfirm, holyreq, kuntae); var response = new { type = "USERAUTH_SAVED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "USERAUTH_DELETE": { int idx = json.idx ?? 0; string result = _bridge.UserAuth_Delete(idx); var response = new { type = "USERAUTH_DELETED", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "USERAUTH_GET_FIELDS": { string result = _bridge.UserAuth_GetFields(); var response = new { type = "USERAUTH_FIELDS_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== 범용 권한 체크 API ===== case "CHECK_AUTH": { string authType = json.authType ?? ""; int requiredLevel = json.requiredLevel ?? 5; string result = _bridge.CheckAuth(authType, requiredLevel); var response = new { type = "CHECK_AUTH_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "GET_MY_AUTH": { string result = _bridge.GetMyAuth(); var response = new { type = "MY_AUTH_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== 프로젝트 검색 API (업무일지용) ===== case "PROJECT_SEARCH": { string keyword = json.keyword ?? ""; string result = _bridge.Project_Search(keyword); var response = new { type = "PROJECT_SEARCH_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "PROJECT_GET_USER_PROJECTS": { string result = _bridge.Project_GetUserProjects(); var response = new { type = "PROJECT_USER_PROJECTS_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "PROJECT_GET_CATEGORIES": { string result = _bridge.Project_GetCategories(); var response = new { type = "PROJECT_CATEGORIES_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "PROJECT_GET_PROCESSES": { string result = _bridge.Project_GetProcesses(); var response = new { type = "PROJECT_PROCESSES_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "PROJECT_GET_LIST": { string statusFilter = json.statusFilter ?? ""; string category = json.category ?? ""; string process = json.process ?? ""; string userFilter = json.userFilter ?? ""; string yearStart = json.yearStart ?? ""; string yearEnd = json.yearEnd ?? ""; string dateType = json.dateType ?? "0"; string result = _bridge.Project_GetList(statusFilter, category, process, userFilter, yearStart, yearEnd, dateType); var response = new { type = "PROJECT_LIST_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "PROJECT_GET_HISTORY": { int projectIdx = json.projectIdx ?? 0; string result = _bridge.Project_GetHistory(projectIdx); var response = new { type = "PROJECT_HISTORY_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "PROJECT_SAVE_HISTORY": { int idx = json.idx ?? 0; int pidx = json.pidx ?? 0; string pdate = json.pdate ?? ""; int progress = json.progress ?? 0; string remark = json.remark ?? ""; string result = _bridge.Project_SaveHistory(idx, pidx, pdate, progress, remark); var response = new { type = "PROJECT_SAVE_HISTORY_RESULT", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "PROJECT_GET_DAILY_MEMO": { int projectIdx = json.projectIdx ?? 0; string result = _bridge.Project_GetDailyMemo(projectIdx); var response = new { type = "PROJECT_DAILY_MEMO_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; // ===== 근태 오류검사 API ===== case "KUNTAE_ERROR_CHECK": { string sd = json.sd ?? ""; string ed = json.ed ?? ""; string result = _bridge.Kuntae_ErrorCheck(sd, ed); var response = new { type = "KUNTAE_ERROR_CHECK_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "KUNTAE_FIX_ERROR": { string pdate = json.pdate ?? ""; string result = _bridge.Kuntae_FixError(pdate); var response = new { type = "KUNTAE_FIX_ERROR_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; case "KUNTAE_FIX_ERRORS": { string dates = JsonConvert.SerializeObject(json.dates); string result = _bridge.Kuntae_FixErrors(dates); var response = new { type = "KUNTAE_FIX_ERRORS_DATA", data = JsonConvert.DeserializeObject(result) }; await Send(socket, JsonConvert.SerializeObject(response)); } break; default: Console.WriteLine($"[WS] Unknown message type: {type}"); break; } } catch (Exception ex) { Console.WriteLine($"[WS] Handle Error: {ex.Message}"); } } private async Task Send(WebSocket socket, string message) { if (_socketLocks.TryGetValue(socket, out var semaphore)) { await semaphore.WaitAsync(); try { if (socket.State == WebSocketState.Open) { byte[] buffer = Encoding.UTF8.GetBytes(message); await socket.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, CancellationToken.None); } } finally { semaphore.Release(); } } } public async void Broadcast(string message) { byte[] buffer = Encoding.UTF8.GetBytes(message); WebSocket[] clientsCopy; lock (_clients) { clientsCopy = _clients.ToArray(); } foreach (var client in clientsCopy) { if (client.State == WebSocketState.Open && _socketLocks.TryGetValue(client, out var semaphore)) { _ = Task.Run(async () => { if (await semaphore.WaitAsync(0)) { try { if (client.State == WebSocketState.Open) { await client.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, CancellationToken.None); } } catch { } finally { semaphore.Release(); } } }); } } } } }