using System.Text; using System.Windows; using AxCopilot.SDK; using AxCopilot.Services; namespace AxCopilot.Handlers; /// /// L22-4: 업무 양식·문서 구조 템플릿 핸들러. "form" 프리픽스로 사용합니다. /// /// 예: form → 전체 양식 카테고리 /// form meeting → 회의록 양식 /// form report → 주간/월간 보고서 양식 /// form email → 이메일 템플릿 (업무·사과·안내·요청) /// form project → 프로젝트 계획서 양식 /// form review → 코드리뷰·성과평가 양식 /// form onboard → 온보딩 체크리스트 /// form <검색어> → 양식 검색 /// Enter → 양식 전체를 클립보드에 복사. /// public class FormHandler : IActionHandler { public string? Prefix => "form"; public PluginMetadata Metadata => new( "업무 양식", "업무 문서 템플릿 — 회의록·보고서·이메일·프로젝트 양식", "1.0", "AX"); private sealed record FormTemplate( string Name, string Description, string Category, string[] Tags, Func Build); private static readonly FormTemplate[] Templates = [ // ── 회의록 (meeting) ───────────────────────────────────────────────── new("회의록 (기본)", "날짜·참석자·안건·결정사항·액션아이템 포함 기본 회의록", "meeting", ["meeting", "회의", "minutes", "미팅"], () => BuildMeeting()), new("주간 팀 회의록", "주간 스탠드업·스프린트 팀 회의에 적합한 형식", "meeting", ["meeting", "weekly", "주간", "팀"], () => BuildWeeklyMeeting()), // ── 보고서 (report) ────────────────────────────────────────────────── new("주간 업무 보고서", "이번 주 완료·진행·예정·이슈 항목 중심 보고서", "report", ["report", "주간", "weekly", "보고"], () => BuildWeeklyReport()), new("월간 업무 보고서", "월간 성과·지표·이슈·다음 달 계획 포함", "report", ["report", "월간", "monthly", "보고"], () => BuildMonthlyReport()), new("업무 현황 보고서", "프로젝트/업무 진행 상황 보고", "report", ["report", "현황", "status"], () => BuildStatusReport()), // ── 이메일 (email) ─────────────────────────────────────────────────── new("업무 요청 이메일", "협조 요청·업무 의뢰 이메일 기본 양식", "email", ["email", "이메일", "mail", "요청"], () => BuildRequestEmail()), new("사과·사안 처리 이메일", "오류·지연 등 사안 발생 시 사과 및 대응 안내 이메일", "email", ["email", "이메일", "사과", "sorry"], () => BuildApologyEmail()), new("공지·안내 이메일", "팀·전사 공지 및 안내 이메일 양식", "email", ["email", "공지", "notice", "안내"], () => BuildNoticeEmail()), // ── 프로젝트 (project) ─────────────────────────────────────────────── new("프로젝트 계획서", "목표·범위·일정·역할·리스크 포함 프로젝트 킥오프 문서", "project", ["project", "프로젝트", "plan", "계획"], () => BuildProjectPlan()), new("프로젝트 완료 보고서", "결과·성과·교훈·후속조치 중심 완료 보고서", "project", ["project", "완료", "close", "closure"], () => BuildProjectClose()), // ── 리뷰·평가 (review) ────────────────────────────────────────────── new("성과 자기평가서", "반기·연간 성과평가 자기 서술 양식", "review", ["review", "평가", "self", "성과"], () => BuildSelfReview()), new("코드 리뷰 체크리스트", "PR·코드리뷰 시 확인사항 체크리스트", "review", ["review", "code", "코드", "pr", "checklist"], () => BuildCodeReview()), // ── 온보딩 (onboard) ───────────────────────────────────────────────── new("신규 입사자 온보딩 체크리스트", "첫 1·2·4주 온보딩 단계별 체크리스트", "onboard", ["onboard", "온보딩", "신입", "입사"], () => BuildOnboard()), ]; private static readonly (string Key, string[] Aliases, string Label)[] Categories = [ ("meeting", ["meeting", "회의", "미팅"], "회의록"), ("report", ["report", "보고", "보고서"], "보고서"), ("email", ["email", "이메일", "mail"], "이메일 템플릿"), ("project", ["project", "프로젝트"], "프로젝트 문서"), ("review", ["review", "리뷰", "평가"], "리뷰·평가"), ("onboard", ["onboard", "온보딩", "입사"], "온보딩"), ]; public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim(); var items = new List(); if (string.IsNullOrWhiteSpace(q)) { items.Add(new LauncherItem("업무 양식 템플릿", "카테고리: meeting · report · email · project · review · onboard", null, null, Symbol: "\uE8A5")); foreach (var (key, _, label) in Categories) { var cnt = Templates.Count(t => t.Category == key); items.Add(new LauncherItem($"form {key}", $"{label} ({cnt}개 양식)", null, ("copy", $"form {key}"), Symbol: "\uE8A5")); } return Task.FromResult>(items); } var kw = q.ToLowerInvariant(); // 카테고리 일치 var cat = Categories.FirstOrDefault(c => c.Aliases.Any(a => a == kw || kw.StartsWith(a))); if (cat.Key != null) { var list = Templates.Where(t => t.Category == cat.Key).ToList(); items.Add(new LauncherItem($"{cat.Label} {list.Count}개", "Enter: 양식 전체를 클립보드에 복사", null, null, Symbol: "\uE8A5")); foreach (var t in list) items.Add(TmplItem(t)); return Task.FromResult>(items); } // 검색 var searched = Templates.Where(t => t.Name.Contains(kw, StringComparison.OrdinalIgnoreCase) || t.Description.Contains(kw, StringComparison.OrdinalIgnoreCase) || t.Tags.Any(tag => tag.Contains(kw, StringComparison.OrdinalIgnoreCase))).ToList(); if (searched.Count == 0) { items.Add(new LauncherItem($"'{q}' 양식을 찾을 수 없습니다", "카테고리: meeting · report · email · project · review · onboard", null, null, Symbol: "\uE783")); return Task.FromResult>(items); } items.Add(new LauncherItem($"'{q}' 검색 결과 {searched.Count}개", "Enter: 양식 전체를 클립보드에 복사", null, null, Symbol: "\uE8A5")); foreach (var t in searched) items.Add(TmplItem(t)); return Task.FromResult>(items); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (item.Data is ("copy", string text)) { try { System.Windows.Application.Current.Dispatcher.Invoke( () => Clipboard.SetText(text)); NotificationService.Notify("업무 양식", "양식을 클립보드에 복사했습니다."); } catch { } } return Task.CompletedTask; } private static LauncherItem TmplItem(FormTemplate t) => new(t.Name, t.Description, null, ("copy", t.Build()), Symbol: "\uE8A5"); // ── 양식 빌더 ──────────────────────────────────────────────────────────── private static string BuildMeeting() { var today = DateTime.Today.ToString("yyyy-MM-dd"); return $""" ■ 회의록 ▸ 일시: {today} ( : ~ : ) ▸ 장소: ▸ 참석자: ▸ 작성자: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▸ 안건 1. 2. 3. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▸ 논의 내용 ▸ 안건 1. - - ▸ 안건 2. - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▸ 결정사항 1. 2. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▸ 액션아이템 (Action Items) 담당자 | 내용 | 기한 ───────────────────────────────────────── | | | | ▸ 다음 회의: """; } private static string BuildWeeklyMeeting() { var today = DateTime.Today.ToString("yyyy-MM-dd"); return $""" ■ 주간 팀 회의록 — {today} ▸ 참석: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▸ 지난 주 회고 (Done) - 완료: - 미완료 및 이유: ▸ 이번 주 목표 (Plan) - - ▸ 이슈 / 블로킹 사항 - ▸ 공유 사항 - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▸ 액션아이템 담당자 | 내용 | 기한 ───────────────────────────────────────── | | """; } private static string BuildWeeklyReport() { var today = DateTime.Today; var mon = today.AddDays(-(int)today.DayOfWeek + 1); var fri = mon.AddDays(4); return $""" ■ 주간 업무 보고서 ▸ 기간: {mon:yyyy-MM-dd} ~ {fri:yyyy-MM-dd} ▸ 보고자: ▸ 소속: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1. 이번 주 완료 업무 □ □ □ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2. 진행 중 업무 (진척률) □ 업무명 진척: 0% - 현황: □ 업무명 진척: 0% - 현황: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3. 다음 주 예정 업무 □ □ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4. 이슈 / 요청 사항 - """; } private static string BuildMonthlyReport() { var today = DateTime.Today; return $""" ■ 월간 업무 보고서 ▸ 기간: {today.Year}년 {today.Month}월 ▸ 보고자: ▸ 소속: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1. 이달의 주요 성과 · · · ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2. 핵심 지표 (KPI) 항목 | 목표 | 실적 | 달성률 ──────────────────────────────────────────────── | | | | | | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3. 이슈 및 리스크 · 이슈: · 대응: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4. 다음 달 계획 · · ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5. 지원 요청 · """; } private static string BuildStatusReport() { var today = DateTime.Today.ToString("yyyy-MM-dd"); return $""" ■ 업무 현황 보고서 ▸ 기준일: {today} ▸ 보고자: ▸ 프로젝트/업무명: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▸ 전체 진행률: [ ] 0% ▸ 단계별 현황 단계 | 시작일 | 완료예정 | 상태 ──────────────────────────────────────────────── 계획 | | | 완료 개발/실행 | | | 진행중 검토 | | | 예정 완료 | | | 예정 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▸ 주요 완료 항목 - ▸ 현재 진행 항목 - ▸ 이슈 / 리스크 - 이슈: - 영향도: 상 / 중 / 하 - 대응방안: ▸ 다음 주요 마일스톤 - 목표: 기한: """; } private static string BuildRequestEmail() { var today = DateTime.Today.ToString("yyyy년 MM월 dd일"); return $""" 수신: 참조: 제목: [협조 요청] 안녕하십니까, [발신자 소속/이름]입니다. [인사말 또는 감사 인사] 다름이 아니라, [요청 배경 및 목적]과 관련하여 아래와 같이 협조를 요청드립니다. ───────────────────────────────────── ▸ 요청 내용 1. 2. ▸ 요청 기한: {today} 기준 __ 영업일 이내 ▸ 담당자: ▸ 관련 자료: (별첨 참조) ───────────────────────────────────── 바쁘신 중에 협조 부탁드리며, 문의사항은 언제든지 연락 주시기 바랍니다. 감사합니다. [이름] 드림 [소속] | [연락처] | [이메일] """; } private static string BuildApologyEmail() { var today = DateTime.Today.ToString("yyyy년 MM월 dd일"); return $""" 수신: 참조: 제목: [사과] 안녕하십니까, [발신자 소속/이름]입니다. {today} [사안 명칭]과 관련하여 [불편·피해] 사항이 발생한 것에 대해 진심으로 사과의 말씀을 드립니다. ───────────────────────────────────── ▸ 발생 경위 [사안 발생 일시·상황 간략 설명] ▸ 영향 범위 [영향 받은 대상·범위] ▸ 원인 분석 [근본 원인] ▸ 즉시 조치 내용 · · ▸ 재발 방지 대책 · · ───────────────────────────────────── 다시 한번 불편을 드린 점 진심으로 사과드리며, 추가 문의사항은 아래 연락처로 알려 주시면 신속히 처리하겠습니다. [이름] 드림 [소속] | [연락처] | [이메일] """; } private static string BuildNoticeEmail() { var today = DateTime.Today.ToString("yyyy년 MM월 dd일"); return $""" 수신: 전체 / 해당팀 참조: 제목: [공지] 안녕하십니까. [소속/담당자]에서 아래와 같이 안내드립니다. ───────────────────────────────────── ▸ 공지 제목: ▸ 적용 일시: {today} 시 분 ~ ▸ 대상: ▸ 주요 내용 1. 2. 3. ▸ 참고 사항 - ───────────────────────────────────── 문의사항은 [담당자명] ([연락처])으로 연락 부탁드립니다. 감사합니다. [소속] [발신자명] 드림 """; } private static string BuildProjectPlan() { var today = DateTime.Today.ToString("yyyy-MM-dd"); return $""" ■ 프로젝트 계획서 ▸ 프로젝트명: ▸ 작성일: {today} ▸ PM / 담당팀: ▸ 문서 버전: v0.1 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1. 배경 및 목적 [프로젝트 추진 배경, 해결하려는 문제, 기대 효과] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2. 범위 (Scope) ▸ 포함 (In-Scope) - - ▸ 제외 (Out-of-Scope) - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3. 주요 일정 (Milestone) 마일스톤 | 목표일 | 담당 ────────────────────────────────────────── 킥오프 | {today} | 요구사항 확정 | | 개발 완료 | | 테스트 완료 | | 오픈 / 완료 | | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4. 역할 및 담당자 (RACI) 역할 | 담당자 | 책임 범위 ────────────────────────────────────────────── PM | | 개발 | | QA | | 이해관계자 | | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5. 리스크 관리 리스크 | 영향도 | 대응방안 ────────────────────────────────────── | 상/중/하| | 상/중/하| ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6. 성공 기준 (Success Criteria) · · """; } private static string BuildProjectClose() { var today = DateTime.Today.ToString("yyyy-MM-dd"); return $""" ■ 프로젝트 완료 보고서 ▸ 프로젝트명: ▸ 완료일: {today} ▸ PM: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1. 최종 결과 요약 [1~2문장으로 프로젝트 완료 내용 요약] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2. 성과 vs 목표 항목 | 목표 | 실적 | 비고 ──────────────────────────────────────────────── 일정 | | | 예산 | | | 품질 기준 | | | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3. 주요 교훈 (Lessons Learned) ▸ 잘 된 점: - ▸ 개선이 필요한 점: - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4. 후속 조치 항목 | 담당자 | 기한 ────────────────────────────────────── | | """; } private static string BuildSelfReview() { var today = DateTime.Today; return $""" ■ 성과 자기평가서 ▸ 평가 기간: {today.Year}년 상반기 / 하반기 / 연간 ▸ 성명: ▸ 소속·직책: ▸ 작성일: {today:yyyy-MM-dd} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1. 기간 내 주요 업무 및 성과 ▸ 업무/프로젝트명: - 내용: - 기여도: - 정량적 성과 (수치 포함): ▸ 업무/프로젝트명: - 내용: - 기여도: - 정량적 성과: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2. 역량 발전 사항 ▸ 향상된 기술·역량: - ▸ 이수한 교육·자격: - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3. 미달 사항 및 원인·개선 계획 ▸ 미달 항목: - 원인: - 개선 방향: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4. 차기 목표 ▸ 업무 목표: - ▸ 역량 개발 계획: - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5. 지원 요청 사항 - """; } private static string BuildCodeReview() { return """ ■ 코드 리뷰 체크리스트 ▸ PR 번호: # ▸ 리뷰어: ▸ 작성자: ▸ 리뷰 일자: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▸ 기능·로직 [ ] 요구사항을 올바르게 구현했는가 [ ] 엣지 케이스를 처리했는가 [ ] 버그 또는 논리적 오류가 없는가 [ ] 비즈니스 규칙을 준수했는가 ▸ 코드 품질 [ ] 변수/함수명이 의미를 명확히 전달하는가 [ ] 코드 중복이 없는가 (DRY 원칙) [ ] 단일 책임 원칙을 지키는가 [ ] 매직 넘버/문자열을 상수로 분리했는가 ▸ 성능·보안 [ ] 불필요한 DB·API 호출이 없는가 [ ] 민감 정보가 코드에 노출되지 않는가 [ ] SQL 인젝션·XSS 등 취약점이 없는가 [ ] 메모리 누수 위험 요소가 없는가 ▸ 테스트 [ ] 핵심 로직의 단위 테스트가 있는가 [ ] 테스트 커버리지가 충분한가 [ ] 기존 테스트가 모두 통과하는가 ▸ 문서·주석 [ ] 복잡한 로직에 주석이 있는가 [ ] API 문서가 업데이트되었는가 [ ] README 또는 CHANGELOG가 갱신되었는가 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▸ 코멘트 - - ▸ 결론: ☐ 승인 ☐ 수정 요청 ☐ 추가 논의 필요 """; } private static string BuildOnboard() { return """ ■ 신규 입사자 온보딩 체크리스트 ▸ 입사자: ▸ 소속·직책: ▸ 입사일: ▸ 담당 멘토: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▸ 입사 첫날 (D-Day) [ ] 사원증·출입 카드 수령 [ ] PC / 업무 장비 수령 [ ] 사내 계정 생성 (이메일·그룹웨어·VPN) [ ] 팀 소개 및 자리 배치 [ ] 보안 서약서 서명 [ ] 사내 규정·복무 안내 확인 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▸ 첫째 주 (1~5일) [ ] 팀 업무·프로세스 소개 [ ] 주요 시스템·툴 접근 권한 설정 [ ] 관련 부서 담당자 소개 [ ] 업무 목표 초안 협의 [ ] 사내 교육 이수 (필수 과정) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▸ 첫째 달 (2~4주) [ ] 주요 프로젝트·업무 파악 [ ] 첫 독립 업무 수행 [ ] 1차 멘토·팀장 면담 [ ] 개인 업무 목표 확정 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ▸ 메모 - """; } }