diff --git a/IssueTracker/IssueRow.cs b/IssueTracker/IssueRow.cs
new file mode 100644
index 0000000..7d4be8d
--- /dev/null
+++ b/IssueTracker/IssueRow.cs
@@ -0,0 +1,24 @@
+using CsvHelper.Configuration.Attributes;
+
+namespace IssueTracker;
+
+public class IssueRow
+{
+ [Name("Issue Type")]
+ public string IssueType { get; set; } = "";
+
+ [Name("Issue key")]
+ public string IssueKey { get; set; } = "";
+
+ [Name("Issue id")]
+ public string IssueId { get; set; } = "";
+
+ public string Summary { get; set; } = "";
+ public string Assignee { get; set; } = "";
+ public string Manager { get; set; } = "";
+ public string Status { get; set; } = "";
+ public string Created { get; set; } = "";
+ public string Updated { get; set; } = "";
+
+ public int WorkingDate { get; set; }
+}
diff --git a/IssueTracker/IssueTracker.csproj b/IssueTracker/IssueTracker.csproj
index ed9781c..cdc69f1 100644
--- a/IssueTracker/IssueTracker.csproj
+++ b/IssueTracker/IssueTracker.csproj
@@ -7,4 +7,8 @@
enable
+
+
+
+
diff --git a/IssueTracker/Program.cs b/IssueTracker/Program.cs
index 0f07ab8..8f0b885 100644
--- a/IssueTracker/Program.cs
+++ b/IssueTracker/Program.cs
@@ -1,10 +1,165 @@
-namespace IssueTracker
+using CsvHelper;
+using CsvHelper.Configuration;
+using System.Globalization;
+
+namespace IssueTracker;
+
+internal class Program
{
- internal class Program
+ static int Main(string[] args)
{
- static void Main(string[] args)
+ if (args.Length < 2)
{
- Console.WriteLine("Hello, World!");
+ Console.WriteLine("Usage: IssueTracker ");
+ return -1;
}
+
+ var inputCsv = args[0];
+ var outputCsv = args[1];
+
+ if (!File.Exists(inputCsv))
+ {
+ Console.WriteLine($"Input CSV not found: {inputCsv}");
+ return -2;
+ }
+
+ Console.WriteLine("========================================");
+ Console.WriteLine(" Issue Tracker started");
+ Console.WriteLine($" Input : {inputCsv}");
+ Console.WriteLine($" Output: {outputCsv}");
+ Console.WriteLine("========================================");
+
+ var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
+ {
+ HasHeaderRecord = true,
+ IgnoreBlankLines = true,
+ BadDataFound = null, // 깨진 데이터 무시
+ MissingFieldFound = null, // 컬럼 누락 무시
+ HeaderValidated = null, // 헤더 검증 비활성화
+ PrepareHeaderForMatch = args => args.Header.Trim() // 헤더 공백 제거
+ };
+
+ // 1️. 최초 실행: output CSV가 없는 경우
+ if (!File.Exists(outputCsv))
+ {
+ Console.WriteLine("Tracking CSV not found. Creating new file.");
+
+ var firstIssues = LoadCsv(inputCsv, csvConfig);
+ Console.WriteLine($"[Initial] Loaded {firstIssues.Count} issues from input CSV");
+
+ SaveCsv(outputCsv, firstIssues, csvConfig);
+
+ Console.WriteLine("Tracking CSV created successfully.");
+ return 0;
+ }
+
+ // 2️. 기존 Tracking CSV 로드
+ var previousIssues = LoadCsv(outputCsv, csvConfig)
+ .ToDictionary(x => x.IssueKey);
+ Console.WriteLine($"[Previous] Loaded {previousIssues.Count} issues from tracking CSV");
+
+ // 3️. 오늘 CSV 로드
+ var todayIssues = LoadCsv(inputCsv, csvConfig)
+ .ToDictionary(x => x.IssueKey);
+ Console.WriteLine($"[Today] Loaded {todayIssues.Count} issues from input CSV");
+
+ var newIssues = new List();
+ var updatedIssues = new List();
+ var retainedIssues = new List();
+
+ int updatedCount = 0;
+ int retainedCount = 0;
+ int newCount = 0;
+
+ // 4️. 기존 모든 이슈 처리 (입력에 없어도 유지)
+ foreach (var (key, old) in previousIssues)
+ {
+ if (todayIssues.TryGetValue(key, out var issue))
+ {
+ // 입력에 존재하는 이슈 -> 업데이트
+ if (string.Equals(old.Status, issue.Status, StringComparison.OrdinalIgnoreCase))
+ {
+ // 상태 유지
+ issue.WorkingDate = old.WorkingDate + 1;
+ }
+ else
+ {
+ // 상태 변경
+ if (string.Equals(issue.Status, "Resolved", StringComparison.OrdinalIgnoreCase))
+ {
+ // Resolved -> 값 유지 + 추적 중지
+ issue.WorkingDate = old.WorkingDate;
+ }
+ else
+ {
+ // 다른 상태 변경 -> 초기화
+ issue.WorkingDate = 0;
+ }
+ }
+
+ updatedIssues.Add(issue);
+ updatedCount++;
+ }
+ else
+ {
+ // 입력에 없는 이슈 -> 기존 데이터 그대로 유지 (아래쪽 배치)
+ retainedIssues.Add(old);
+ retainedCount++;
+ //Console.WriteLine($"[Retained] Issue {key} not in input CSV, keeping in output");
+ }
+ }
+
+ // 5️. 입력에는 있지만 기존 추적에 없던 신규 이슈 추가 (위쪽 배치)
+ foreach (var (key, issue) in todayIssues)
+ {
+ if (!previousIssues.ContainsKey(key))
+ {
+ // 신규 Issue
+ issue.WorkingDate = 0;
+ newIssues.Add(issue);
+ newCount++;
+ Console.WriteLine($"[New] Issue {key} added to tracking");
+ }
+ }
+
+ Console.WriteLine("----------------------------------------");
+ Console.WriteLine($"Updated: {updatedCount}, Retained: {retainedCount}, New: {newCount}");
+ Console.WriteLine("----------------------------------------");
+
+ // 6️. 정렬: 신규 -> 업데이트 -> 추적 중단
+ var result = new List();
+ result.AddRange(newIssues);
+ result.AddRange(updatedIssues);
+ result.AddRange(retainedIssues);
+
+ Console.WriteLine($"Total output rows: {result.Count}");
+ Console.WriteLine("----------------------------------------");
+
+ // 7️. 결과 저장
+ SaveCsv(outputCsv, result, csvConfig);
+
+ Console.WriteLine("Tracking CSV updated successfully.");
+ return 0;
+ }
+
+ private static List LoadCsv(string path, CsvConfiguration config)
+ {
+ using var reader = new StreamReader(path);
+ using var csv = new CsvReader(reader, config);
+
+ return csv.GetRecords().ToList();
+ }
+
+ private static void SaveCsv(
+ string path,
+ List data,
+ CsvConfiguration config)
+ {
+ using var writer = new StreamWriter(path, false, System.Text.Encoding.UTF8);
+ using var csv = new CsvWriter(writer, config);
+
+ csv.WriteHeader();
+ csv.NextRecord();
+ csv.WriteRecords(data);
}
}