203 lines
9.1 KiB
C#
203 lines
9.1 KiB
C#
// GanttJsonParser: 행 기반(STDTxx/FNDTxx/DURxx) 간트 데이터 파서
|
|
// 요구: 오류가 있어도 삭제하지 말 것.
|
|
#nullable enable
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Text.RegularExpressions;
|
|
using UnityEngine;
|
|
|
|
namespace SHI.modal
|
|
{
|
|
/// <summary>
|
|
/// isop_chart.json과 같은 행 배열(딕셔너리)의 STDTxx / FNDTxx / DURxx 패턴을 해석하여 <see cref="GanttChartData"/>를 생성합니다.
|
|
/// </summary>
|
|
public static class GanttJsonParser
|
|
{
|
|
private static readonly Regex StartRegex = new Regex("^STDT([0-9A-FM]{2})$", RegexOptions.Compiled);
|
|
private static readonly Regex EndRegex = new Regex("^FNDT([0-9A-FM]{2})$", RegexOptions.Compiled);
|
|
private static readonly Regex DurRegex = new Regex("^DUR([0-9A-FM]{2})$", RegexOptions.Compiled);
|
|
private const string DateFormat = "yyyyMMdd";
|
|
|
|
/// <summary>
|
|
/// JSON 배열 문자열을 파싱해 Rows/Segments/MinDate/MaxDate를 채웁니다.
|
|
/// Duration 누락 시 (End-Start)+1을 사용합니다.
|
|
/// </summary>
|
|
public static GanttChartData Parse(string json, bool computeDurationIfMissing = true)
|
|
{
|
|
var data = new GanttChartData();
|
|
if (string.IsNullOrWhiteSpace(json)) return data;
|
|
|
|
List<Dictionary<string, object>>? rows;
|
|
try
|
|
{
|
|
// Newtonsoft.Json 사용 (Assembly 내 참조 필요). 실패 시 경고 후 빈 데이터 반환.
|
|
rows = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(json);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogWarning($"GanttJsonParser.Parse: JSON deserialize failed: {ex.Message}");
|
|
return data;
|
|
}
|
|
if (rows == null) return data;
|
|
|
|
DateTime? min = null; DateTime? max = null;
|
|
|
|
foreach (var raw in rows)
|
|
{
|
|
if (raw == null) continue;
|
|
var row = new BlockScheduleRow
|
|
{
|
|
ProjNo = GetStr(raw, "PROJ_NO"),
|
|
BlockNo = GetStr(raw, "BLK_NO"),
|
|
L1 = GetStr(raw, "L1"),
|
|
L2 = GetStr(raw, "L2"),
|
|
L3 = GetStr(raw, "L3"),
|
|
L4 = GetStr(raw, "L4"),
|
|
L5 = GetStr(raw, "L5"),
|
|
L6 = GetStr(raw, "L6"),
|
|
L7 = GetStr(raw, "L7"),
|
|
L8 = GetStr(raw, "L8"),
|
|
// 추가 메타
|
|
BlkDsc = GetStr(raw, "BLK_DSC"),
|
|
ShipType = GetStr(raw, "SHIP_TYPE"),
|
|
ShrGb = GetStr(raw, "SHR_GB"),
|
|
Lth = GetFloat(raw, "LTH"),
|
|
Bth = GetFloat(raw, "BTH"),
|
|
Hgt = GetFloat(raw, "HGT"),
|
|
NetWgt = GetFloat(raw, "NET_WGT"),
|
|
EqupWgt = GetFloat(raw, "EQUP_WGT"),
|
|
LftWgt = GetFloat(raw, "LFT_WGT"),
|
|
WkType = GetStr(raw, "WK_TYPE"),
|
|
Wc31 = GetStr(raw, "WC31"),
|
|
};
|
|
|
|
var startMap = new Dictionary<string, DateTime>();
|
|
var endMap = new Dictionary<string, DateTime>();
|
|
var durMap = new Dictionary<string, int>();
|
|
|
|
foreach (var kv in raw)
|
|
{
|
|
if (kv.Key == null || kv.Value == null) continue;
|
|
var val = kv.Value.ToString();
|
|
if (string.IsNullOrWhiteSpace(val)) continue;
|
|
|
|
var sm = StartRegex.Match(kv.Key);
|
|
if (sm.Success)
|
|
{
|
|
if (TryParseDate(val, out var sdt))
|
|
{
|
|
var code = sm.Groups[1].Value;
|
|
if (startMap.ContainsKey(code)) Debug.Log($"GanttJsonParser: duplicate STDT for code={code}, last value wins.");
|
|
startMap[code] = sdt; UpdateMinMax(ref min, ref max, sdt); continue;
|
|
}
|
|
else { Debug.LogWarning($"GanttJsonParser: start date parse failed key={kv.Key} value={val}"); continue; }
|
|
}
|
|
var em = EndRegex.Match(kv.Key);
|
|
if (em.Success)
|
|
{
|
|
if (TryParseDate(val, out var edt))
|
|
{
|
|
var code = em.Groups[1].Value;
|
|
if (endMap.ContainsKey(code)) Debug.Log($"GanttJsonParser: duplicate FNDT for code={code}, last value wins.");
|
|
endMap[code] = edt; UpdateMinMax(ref min, ref max, edt); continue;
|
|
}
|
|
else { Debug.LogWarning($"GanttJsonParser: end date parse failed key={kv.Key} value={val}"); continue; }
|
|
}
|
|
var dm = DurRegex.Match(kv.Key);
|
|
if (dm.Success)
|
|
{
|
|
var code = dm.Groups[1].Value;
|
|
if (int.TryParse(val, out var dur))
|
|
{
|
|
if (durMap.ContainsKey(code)) Debug.Log($"GanttJsonParser: duplicate DUR for code={code}, last value wins.");
|
|
durMap[code] = dur; continue;
|
|
}
|
|
else { Debug.LogWarning($"GanttJsonParser: duration parse failed key={kv.Key} value={val}"); continue; }
|
|
}
|
|
}
|
|
|
|
var codes = new HashSet<string>(startMap.Keys);
|
|
codes.UnionWith(endMap.Keys);
|
|
codes.UnionWith(durMap.Keys);
|
|
|
|
foreach (var code in codes)
|
|
{
|
|
var range = new DateRange();
|
|
if (startMap.TryGetValue(code, out var s)) range.Start = s;
|
|
if (endMap.TryGetValue(code, out var e)) range.End = e;
|
|
if (durMap.TryGetValue(code, out var d)) range.Duration = d;
|
|
else if (computeDurationIfMissing && range.Start.HasValue && range.End.HasValue)
|
|
range.Duration = (int)(range.End.Value.Date - range.Start.Value.Date).TotalDays + 1;
|
|
|
|
// Start > End 정책: Skip + Warning
|
|
if (range.Start.HasValue && range.End.HasValue && range.End.Value.Date < range.Start.Value.Date)
|
|
{
|
|
Debug.LogWarning($"GanttJsonParser: Start > End. Skip record block={row.BlockNo} code={code} start={range.Start:yyyyMMdd} end={range.End:yyyyMMdd}");
|
|
continue;
|
|
}
|
|
|
|
row.Periods[code] = range;
|
|
|
|
if (range.Start.HasValue && range.End.HasValue)
|
|
{
|
|
// Flat segment 동시 생성 (유효한 경우만)
|
|
data.Segments.Add(new ScheduleSegment
|
|
{
|
|
ItemKey = (row.BlockNo ?? string.Empty) + "|" + code,
|
|
ItemId = Guid.Empty,
|
|
Start = range.Start.Value,
|
|
End = range.End.Value,
|
|
Progress = 0f,
|
|
Type = code
|
|
});
|
|
}
|
|
}
|
|
|
|
data.Rows.Add(row);
|
|
}
|
|
|
|
data.MinDate = min; data.MaxDate = max;
|
|
return data;
|
|
}
|
|
|
|
private static void UpdateMinMax(ref DateTime? min, ref DateTime? max, DateTime v)
|
|
{
|
|
if (!min.HasValue || v < min.Value) min = v;
|
|
if (!max.HasValue || v > max.Value) max = v;
|
|
}
|
|
|
|
private static bool TryParseDate(string s, out DateTime dt)
|
|
=> DateTime.TryParseExact(s, DateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dt);
|
|
|
|
private static string? GetStr(Dictionary<string, object> dict, string key)
|
|
=> dict.TryGetValue(key, out var v) ? v?.ToString() : null;
|
|
|
|
private static float? GetFloat(Dictionary<string, object> dict, string key)
|
|
{
|
|
if (!dict.TryGetValue(key, out var v) || v == null) return null;
|
|
try
|
|
{
|
|
switch (v)
|
|
{
|
|
case float f: return f;
|
|
case double d: return (float)d;
|
|
case decimal m: return (float)m;
|
|
case int i: return i;
|
|
case long l: return l;
|
|
case short s: return s;
|
|
case string str:
|
|
if (string.IsNullOrWhiteSpace(str)) return null;
|
|
if (float.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out var pf)) return pf;
|
|
if (float.TryParse(str, NumberStyles.Any, CultureInfo.CurrentCulture, out pf)) return pf;
|
|
return null;
|
|
default:
|
|
// 마지막 시도: System.Convert
|
|
return (float)Convert.ToDouble(v, CultureInfo.InvariantCulture);
|
|
}
|
|
}
|
|
catch { return null; }
|
|
}
|
|
}
|
|
}
|