초기 커밋: SheetMe 서식생성기 (P0~P5 완료 상태)
레거시 서식생성기(VB.NET WinForms) 대체용 C#/.NET 10 WPF 디자이너. 기준선: 실DB 활성 디자인 1,271건 왕복 의미론 diff 0 / 예외 0, 단위 테스트 49/49. 이 커밋에 함께 포함된 자격증명 분리: - appsettings.json 을 __HOST__/__PASSWORD__ 플레이스홀더로 전환 - 실접속 정보는 appsettings.Development.json 으로 분리(.gitignore 제외, csproj Debug 조건부 복사라 Release 산출물에 실리지 않음) - ConfigLoader 를 환경변수 > Development > appsettings 순 레이어링으로 변경, 미치환 플레이스홀더는 '미설정'으로 간주 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,426 @@
|
||||
using Oracle.ManagedDataAccess.Client;
|
||||
using SheetMe.Core.Models;
|
||||
using SheetMe.Core.Serialization;
|
||||
|
||||
namespace SheetMe.Data.Stores;
|
||||
|
||||
/// <summary>서식 목록 1건(열기 대화상자)</summary>
|
||||
public sealed record SheetSummary(string ShtCod, string Name, bool HasDesign);
|
||||
|
||||
/// <summary>디자인 버전 1건</summary>
|
||||
public sealed record DesignVersionInfo(decimal SdgKey, string UpdDtm, string UpdUid, bool Deleted);
|
||||
|
||||
/// <summary>
|
||||
/// 레거시 테이블(E_ShtMst/E_SdgMst/E_SctMst) 기반 서식 저장소 — 레거시 디자이너와 동일 저장 의미론.
|
||||
/// 저장 흐름(bzSaveSheetDesignNControlInfo 이식, 단일 트랜잭션):
|
||||
/// ShtCneYon='Y'+활성행 존재 → 제자리 갱신(E_SctMst 는 신규 이름만 추가),
|
||||
/// 그 외 → 기존 활성행 SdgDelYon='Y' 후 신규 행 INSERT + E_SctMst 전체 재생성.
|
||||
/// 채번: 전용 시퀀스 NEXTVAL 우선, 부재 시 MAX+1 폴백(트랜잭션 내 — [200]SheetMe 검증 방식).
|
||||
/// </summary>
|
||||
public sealed class OracleLegacyFormStore
|
||||
{
|
||||
#region Member Fields
|
||||
private readonly string connectionString;
|
||||
private readonly LegacyXmlSerializer serializer = new();
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
public OracleLegacyFormStore(string connectionString)
|
||||
{
|
||||
this.connectionString = connectionString;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods - 조회
|
||||
/// <summary>서식 목록 검색 — 공백제거 UPPER LIKE 토큰 AND 매칭, 스캔서식(ShtTyp='S') 제외</summary>
|
||||
public List<SheetSummary> ListSheets(string? keyword, int max = 300)
|
||||
{
|
||||
using var connection = Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.BindByName = true;
|
||||
|
||||
var where = "WHERE NVL(s.ShtTyp,' ') <> 'S'";
|
||||
var tokens = (keyword ?? string.Empty)
|
||||
.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
for (var i = 0; i < tokens.Length; i++)
|
||||
{
|
||||
where += $" AND (UPPER(REPLACE(s.ShtKorNam,' ','')) LIKE :k{i} OR UPPER(s.ShtCod) LIKE :k{i})";
|
||||
command.Parameters.Add(new OracleParameter($"k{i}", $"%{tokens[i].ToUpperInvariant()}%"));
|
||||
}
|
||||
|
||||
command.CommandText =
|
||||
"SELECT * FROM (" +
|
||||
" SELECT s.ShtCod, s.ShtKorNam," +
|
||||
" (SELECT COUNT(*) FROM E_SdgMst d WHERE d.SdgShtCod = s.ShtCod AND NVL(d.SdgDelYon,' ') <> 'Y') AS DesignCount" +
|
||||
$" FROM E_ShtMst s {where} ORDER BY s.ShtCod" +
|
||||
$") WHERE ROWNUM <= {max}";
|
||||
|
||||
var result = new List<SheetSummary>();
|
||||
using var reader = command.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
result.Add(new SheetSummary(
|
||||
reader.GetString(0),
|
||||
reader.IsDBNull(1) ? string.Empty : reader.GetString(1),
|
||||
reader.GetDecimal(2) > 0));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>활성(비삭제 최신) 디자인 로드 — 없으면 null. FormId/Title/SourceSdgKey 채움</summary>
|
||||
public FormDocument? LoadActiveDesign(string shtCod)
|
||||
{
|
||||
using var connection = Open();
|
||||
|
||||
var sdgKey = ResolveActiveSdgKey(connection, null, shtCod);
|
||||
if (sdgKey == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using var command = connection.CreateCommand();
|
||||
command.BindByName = true;
|
||||
command.CommandText = "SELECT SdgDesign FROM E_SdgMst WHERE SdgKey = :k";
|
||||
command.Parameters.Add(new OracleParameter("k", sdgKey));
|
||||
var xml = command.ExecuteScalar() as string;
|
||||
if (string.IsNullOrWhiteSpace(xml))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var document = serializer.Read(xml);
|
||||
document.FormId = shtCod;
|
||||
document.Title = QueryTitle(connection, shtCod) ?? shtCod;
|
||||
document.Meta.SourceSdgKey = sdgKey;
|
||||
return document;
|
||||
}
|
||||
|
||||
/// <summary>활성 디자인 원문 XML — (SdgKey, XML), 없으면 null (일괄 왕복 스모크용)</summary>
|
||||
public (decimal SdgKey, string Xml)? LoadActiveDesignRaw(string shtCod)
|
||||
{
|
||||
using var connection = Open();
|
||||
var sdgKey = ResolveActiveSdgKey(connection, null, shtCod);
|
||||
if (sdgKey == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
using var command = connection.CreateCommand();
|
||||
command.BindByName = true;
|
||||
command.CommandText = "SELECT SdgDesign FROM E_SdgMst WHERE SdgKey = :k";
|
||||
command.Parameters.Add(new OracleParameter("k", sdgKey));
|
||||
return command.ExecuteScalar() is string xml && !string.IsNullOrWhiteSpace(xml)
|
||||
? (sdgKey, xml)
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>디자인 버전의 E_SctMst 행 수(검증용)</summary>
|
||||
public int CountSctRows(decimal sdgKey)
|
||||
{
|
||||
using var connection = Open();
|
||||
return Convert.ToInt32(Scalar(connection, null,
|
||||
"SELECT COUNT(*) FROM E_SctMst WHERE SctSdgKey = :k", new OracleParameter("k", sdgKey))!);
|
||||
}
|
||||
|
||||
/// <summary>서식의 제자리 갱신 여부(ShtCneYon)</summary>
|
||||
public string GetShtCneYon(string shtCod)
|
||||
{
|
||||
using var connection = Open();
|
||||
return Scalar(connection, null,
|
||||
"SELECT NVL(ShtCneYon,' ') FROM E_ShtMst WHERE ShtCod = :s",
|
||||
new OracleParameter("s", shtCod)) as string ?? " ";
|
||||
}
|
||||
|
||||
/// <summary>특정 버전(SdgKey) 디자인 로드 — 이력 열람/복원용. 없으면 null</summary>
|
||||
public FormDocument? LoadDesignByKey(string shtCod, decimal sdgKey)
|
||||
{
|
||||
using var connection = Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.BindByName = true;
|
||||
command.CommandText = "SELECT SdgDesign FROM E_SdgMst WHERE SdgKey = :k";
|
||||
command.Parameters.Add(new OracleParameter("k", sdgKey));
|
||||
if (command.ExecuteScalar() is not string xml || string.IsNullOrWhiteSpace(xml))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var document = serializer.Read(xml);
|
||||
document.FormId = shtCod;
|
||||
document.Title = QueryTitle(connection, shtCod) ?? shtCod;
|
||||
document.Meta.SourceSdgKey = sdgKey;
|
||||
return document;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 서식의 Spread 디자인들(E_SpdMst) — 컨트롤명(SpdName) → FarPoint XML.
|
||||
/// SpdShtCod 는 CHAR 패딩 컬럼이라 TRIM 비교(실측).
|
||||
/// </summary>
|
||||
public Dictionary<string, string> LoadSpreadDesigns(string shtCod)
|
||||
{
|
||||
var result = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
using var connection = Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.BindByName = true;
|
||||
command.CommandText =
|
||||
"SELECT TRIM(SpdName), SpdDesign FROM E_SpdMst " +
|
||||
"WHERE TRIM(SpdShtCod) = :s AND NVL(SpdDelYon,' ') <> 'Y'";
|
||||
command.Parameters.Add(new OracleParameter("s", shtCod));
|
||||
using var reader = command.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
if (!reader.IsDBNull(1))
|
||||
{
|
||||
result[reader.GetString(0)] = reader.GetString(1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>디자인 버전 이력(본문 제외)</summary>
|
||||
public List<DesignVersionInfo> ListVersions(string shtCod)
|
||||
{
|
||||
using var connection = Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.BindByName = true;
|
||||
command.CommandText =
|
||||
"SELECT SdgKey, NVL(SdgUpdDtm,' '), NVL(SdgUidCod,' '), NVL(SdgDelYon,' ') " +
|
||||
"FROM E_SdgMst WHERE SdgShtCod = :s ORDER BY SdgKey DESC";
|
||||
command.Parameters.Add(new OracleParameter("s", shtCod));
|
||||
|
||||
var result = new List<DesignVersionInfo>();
|
||||
using var reader = command.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
result.Add(new DesignVersionInfo(
|
||||
reader.GetDecimal(0), reader.GetString(1).Trim(), reader.GetString(2).Trim(),
|
||||
reader.GetString(3) == "Y"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods - 저장
|
||||
/// <summary>
|
||||
/// 디자인 저장 — 레거시 동일 의미론(제자리 갱신 vs 버저닝), 단일 트랜잭션.
|
||||
/// 반환: 저장된 SdgKey.
|
||||
/// </summary>
|
||||
public decimal SaveDesign(FormDocument document, string uid)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(document.FormId))
|
||||
{
|
||||
throw new InvalidOperationException("서식 코드(FormId)가 없습니다.");
|
||||
}
|
||||
|
||||
var mergedXml = serializer.Write(document);
|
||||
var now = DateTime.Now.ToString("yyyyMMddHHmm"); // 레거시 SystemDateTime 포맷(12자리)
|
||||
|
||||
using var connection = Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
var shtCneYon = Scalar(connection, transaction,
|
||||
"SELECT NVL(ShtCneYon,' ') FROM E_ShtMst WHERE ShtCod = :s",
|
||||
new OracleParameter("s", document.FormId)) as string;
|
||||
if (shtCneYon is null)
|
||||
{
|
||||
throw new InvalidOperationException($"E_ShtMst 에 서식이 없습니다: {document.FormId}");
|
||||
}
|
||||
|
||||
var activeSdgKey = ResolveActiveSdgKey(connection, transaction, document.FormId);
|
||||
var updateInPlace = shtCneYon == "Y" && activeSdgKey > 0;
|
||||
|
||||
decimal sdgKey;
|
||||
if (updateInPlace)
|
||||
{
|
||||
// 제자리 갱신 — 레거시 ShtCneYon='Y' 경로
|
||||
sdgKey = activeSdgKey;
|
||||
NonQuery(connection, transaction,
|
||||
"UPDATE E_SdgMst SET SdgDesign = :d, SdgUidCod = :u, SdgUpdDtm = :t WHERE SdgKey = :k",
|
||||
new OracleParameter("d", OracleDbType.Clob) { Value = mergedXml },
|
||||
new OracleParameter("u", uid),
|
||||
new OracleParameter("t", now),
|
||||
new OracleParameter("k", sdgKey));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 기존 활성행 히스토리 처리 후 신규 행
|
||||
if (activeSdgKey > 0)
|
||||
{
|
||||
NonQuery(connection, transaction,
|
||||
"UPDATE E_SdgMst SET SdgDelYon = 'Y' WHERE SdgKey = :k",
|
||||
new OracleParameter("k", activeSdgKey));
|
||||
}
|
||||
sdgKey = NextVal(connection, transaction, "E_SDGMST_SDGKEY",
|
||||
"SELECT NVL(MAX(SdgKey),0)+1 FROM E_SdgMst");
|
||||
NonQuery(connection, transaction,
|
||||
"INSERT INTO E_SdgMst (SdgKey, SdgShtCod, SdgDesign, SdgUidCod, SdgUpdDtm, SdgDelYon, SdgDelUid, SdgDelDtm) " +
|
||||
"VALUES (:k, :s, :d, :u, :t, '', '', '')",
|
||||
new OracleParameter("k", sdgKey),
|
||||
new OracleParameter("s", document.FormId),
|
||||
new OracleParameter("d", OracleDbType.Clob) { Value = mergedXml },
|
||||
new OracleParameter("u", uid),
|
||||
new OracleParameter("t", now));
|
||||
}
|
||||
|
||||
SaveSctMst(connection, transaction, sdgKey, mergedXml, updateInPlace);
|
||||
|
||||
transaction.Commit();
|
||||
document.Meta.SourceSdgKey = sdgKey;
|
||||
document.Meta.UpdatedBy = uid;
|
||||
document.Meta.UpdatedAt = now;
|
||||
return sdgKey;
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>E_ShtMst 존재 여부</summary>
|
||||
public bool SheetExists(string shtCod)
|
||||
{
|
||||
using var connection = Open();
|
||||
return Convert.ToInt32(Scalar(connection, null,
|
||||
"SELECT COUNT(*) FROM E_ShtMst WHERE ShtCod = :s", new OracleParameter("s", shtCod))!) > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 신규 서식 최소 등록 — 실DB 디자이너 서식 관행값(ShtTyp='D', 사용/디자인/컨트롤/EMR 'Y', 열람권한 'A' 등).
|
||||
/// 상세 속성(권한/출력 등)은 레거시 기록지정보 마스터에서 후속 설정.
|
||||
/// </summary>
|
||||
public void RegisterSheet(string shtCod, string korName, string? clsCod, string uid)
|
||||
{
|
||||
if (shtCod.Length is 0 or > 10)
|
||||
{
|
||||
throw new ArgumentException("서식 코드는 1~10자여야 합니다.");
|
||||
}
|
||||
|
||||
using var connection = Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
NonQuery(connection, transaction,
|
||||
"INSERT INTO E_ShtMst (ShtCod, ShtAdpDte, ShtKorNam, ShtClsCod, ShtTyp, ShtUseYon, " +
|
||||
"ShtUsrDesYon, ShtUseSdg, ShtUseSct, ShtUseEmr, ShtRedAth, ShtWrtTyp, ShtCneYon, ShtTraSto, " +
|
||||
"ShtEleSig, ShtUpdUid, ShtUpdDtm) " +
|
||||
"VALUES (:cod, :adp, :nam, :cls, 'D', 'Y', 'Y', 'Y', 'Y', 'Y', 'A', '9A', 'N', 'N', 'N', :uid, :dtm)",
|
||||
new OracleParameter("cod", shtCod),
|
||||
new OracleParameter("adp", DateTime.Now.ToString("yyyyMMdd")),
|
||||
new OracleParameter("nam", korName),
|
||||
new OracleParameter("cls", string.IsNullOrWhiteSpace(clsCod) ? (object)DBNull.Value : clsCod),
|
||||
new OracleParameter("uid", uid),
|
||||
new OracleParameter("dtm", DateTime.Now.ToString("yyyyMMddHHmm")));
|
||||
transaction.Commit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveSctMst(OracleConnection connection, OracleTransaction transaction,
|
||||
decimal sdgKey, string mergedXml, bool updateInPlace)
|
||||
{
|
||||
var rows = SctMstXmlWalker.Walk(mergedXml);
|
||||
|
||||
HashSet<string>? existingNames = null;
|
||||
if (updateInPlace)
|
||||
{
|
||||
existingNames = new HashSet<string>(StringComparer.Ordinal);
|
||||
using var query = connection.CreateCommand();
|
||||
query.Transaction = transaction;
|
||||
query.BindByName = true;
|
||||
query.CommandText = "SELECT SctObjNam FROM E_SctMst WHERE SctSdgKey = :k";
|
||||
query.Parameters.Add(new OracleParameter("k", sdgKey));
|
||||
using var reader = query.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
existingNames.Add(reader.GetString(0));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
// 제자리 갱신: 기존 이름은 유지(레거시 — SctKey 보존), 신규 이름만 추가
|
||||
if (existingNames is not null && existingNames.Contains(row.ObjName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var sctKey = NextVal(connection, transaction, "E_SCTMST_SCTKEY",
|
||||
"SELECT NVL(MAX(SctKey),0)+1 FROM E_SctMst");
|
||||
NonQuery(connection, transaction,
|
||||
"INSERT INTO E_SctMst (SctKey, SctSdgKey, SctObjNam, SctObjID, SctObjTyp, SctParObj, SctObjSeq, SctObjTxt) " +
|
||||
"VALUES (:k, :sdg, :nam, :oid, :typ, :par, :seq, :txt)",
|
||||
new OracleParameter("k", sctKey),
|
||||
new OracleParameter("sdg", sdgKey),
|
||||
new OracleParameter("nam", row.ObjName),
|
||||
new OracleParameter("oid", row.ObjId),
|
||||
new OracleParameter("typ", row.ObjType),
|
||||
new OracleParameter("par", row.ParentId),
|
||||
new OracleParameter("seq", row.ObjSeq.Length == 0 ? "0" : row.ObjSeq),
|
||||
new OracleParameter("txt", row.ObjText.Length == 0 ? (object)DBNull.Value : row.ObjText));
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods - Private
|
||||
private OracleConnection Open()
|
||||
{
|
||||
var connection = new OracleConnection(connectionString);
|
||||
connection.Open();
|
||||
return connection;
|
||||
}
|
||||
|
||||
/// <summary>활성(비삭제 최신) SdgKey — 없으면 0 ([200] SdgKeyResolver 의미론)</summary>
|
||||
private static decimal ResolveActiveSdgKey(OracleConnection connection, OracleTransaction? transaction, string shtCod)
|
||||
{
|
||||
var value = Scalar(connection, transaction,
|
||||
"SELECT NVL((SELECT SdgKey FROM (SELECT SdgKey FROM E_SdgMst " +
|
||||
"WHERE SdgShtCod = :s AND NVL(SdgDelYon,' ') <> 'Y' ORDER BY SdgUpdDtm DESC, SdgKey DESC) " +
|
||||
"WHERE ROWNUM = 1), 0) FROM DUAL",
|
||||
new OracleParameter("s", shtCod));
|
||||
return value is null or DBNull ? 0m : Convert.ToDecimal(value);
|
||||
}
|
||||
|
||||
private static string? QueryTitle(OracleConnection connection, string shtCod)
|
||||
=> Scalar(connection, null,
|
||||
"SELECT ShtKorNam FROM E_ShtMst WHERE ShtCod = :s", new OracleParameter("s", shtCod)) as string;
|
||||
|
||||
/// <summary>채번 — 전용 시퀀스 NEXTVAL 우선, 시퀀스 부재(ORA-02289/00942) 시 MAX+1 폴백</summary>
|
||||
private static decimal NextVal(OracleConnection connection, OracleTransaction transaction, string sequence, string fallbackSql)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.ToDecimal(Scalar(connection, transaction, $"SELECT {sequence}.NEXTVAL FROM DUAL")!);
|
||||
}
|
||||
catch (OracleException ex) when (ex.Number is 2289 or 942)
|
||||
{
|
||||
return Convert.ToDecimal(Scalar(connection, transaction, fallbackSql)!);
|
||||
}
|
||||
}
|
||||
|
||||
private static object? Scalar(OracleConnection connection, OracleTransaction? transaction, string sql, params OracleParameter[] parameters)
|
||||
{
|
||||
using var command = connection.CreateCommand();
|
||||
if (transaction is not null)
|
||||
{
|
||||
command.Transaction = transaction;
|
||||
}
|
||||
command.BindByName = true;
|
||||
command.CommandText = sql;
|
||||
command.Parameters.AddRange(parameters);
|
||||
return command.ExecuteScalar();
|
||||
}
|
||||
|
||||
private static void NonQuery(OracleConnection connection, OracleTransaction transaction, string sql, params OracleParameter[] parameters)
|
||||
{
|
||||
using var command = connection.CreateCommand();
|
||||
command.Transaction = transaction;
|
||||
command.BindByName = true;
|
||||
command.CommandText = sql;
|
||||
command.Parameters.AddRange(parameters);
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
using Oracle.ManagedDataAccess.Client;
|
||||
|
||||
namespace SheetMe.Data.Stores;
|
||||
|
||||
/// <summary>상용구 1건</summary>
|
||||
public sealed record RecordWordInfo(decimal Key, string Value, decimal DisplaySeq);
|
||||
|
||||
/// <summary>
|
||||
/// 서식별 상용구(E_SHTWRDMST) 저장소 — 1단계(SHTSTPLVL=1) 문구 CRUD.
|
||||
/// 런타임 팝업(MRecordWord.dtRecWordMst) 조회 쿼리와 호환: SHTCOD+SHTSTPLVL=1, SHTDSPSEQ 정렬, 루트 SHTPARKEY=0.
|
||||
/// 레거시에는 등록 UI 가 없어(INSERT 코드 부재 — DB 직접 관리) 본 디자이너가 관리 도구 역할을 겸한다.
|
||||
/// </summary>
|
||||
public sealed class RecordWordStore
|
||||
{
|
||||
#region Member Fields
|
||||
private readonly string connectionString;
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
public RecordWordStore(string connectionString)
|
||||
{
|
||||
this.connectionString = connectionString;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
/// <summary>서식의 1단계 상용구 목록(표시순)</summary>
|
||||
public List<RecordWordInfo> List(string shtCod)
|
||||
{
|
||||
using var connection = Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.BindByName = true;
|
||||
command.CommandText =
|
||||
"SELECT ShtWrdKey, ShtWrdVal, NVL(ShtDspSeq, 0) FROM E_SHTWRDMST " +
|
||||
"WHERE ShtCod = :s AND ShtStpLvl = 1 ORDER BY ShtDspSeq, ShtWrdKey";
|
||||
command.Parameters.Add(new OracleParameter("s", shtCod));
|
||||
|
||||
var result = new List<RecordWordInfo>();
|
||||
using var reader = command.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
result.Add(new RecordWordInfo(reader.GetDecimal(0), reader.GetString(1), reader.GetDecimal(2)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 상용구 추가 — 키는 트리거 자동채번 우선(RETURNING 회수, 실측: 지정 키를 트리거가 덮어씀),
|
||||
/// 트리거 부재(ORA-01400) 시 MAX+1 명시 폴백. 반환: 실제 저장된 키.
|
||||
/// </summary>
|
||||
public decimal Add(string shtCod, string value, string uid)
|
||||
{
|
||||
using var connection = Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
var seq = Convert.ToDecimal(Scalar(connection, transaction,
|
||||
"SELECT NVL(MAX(ShtDspSeq),0)+1 FROM E_SHTWRDMST WHERE ShtCod = :s AND ShtStpLvl = 1",
|
||||
new OracleParameter("s", shtCod))!);
|
||||
|
||||
decimal key;
|
||||
try
|
||||
{
|
||||
// 트리거 채번 경로 — 실제 키를 RETURNING 으로 회수
|
||||
using var command = connection.CreateCommand();
|
||||
command.Transaction = transaction;
|
||||
command.BindByName = true;
|
||||
command.CommandText =
|
||||
"INSERT INTO E_SHTWRDMST (ShtCod, ShtParKey, ShtWrdVal, ShtStpLvl, ShtDspSeq, ShtStt, " +
|
||||
"ShtUpdUid, ShtUpdDtf, ShtFstUid, ShtFstDtf) " +
|
||||
"VALUES (:s, 0, :v, 1, :q, 'U', :u, :d, :u, :d) " +
|
||||
"RETURNING ShtWrdKey INTO :outKey";
|
||||
command.Parameters.Add(new OracleParameter("s", shtCod));
|
||||
command.Parameters.Add(new OracleParameter("v", value));
|
||||
command.Parameters.Add(new OracleParameter("q", seq));
|
||||
command.Parameters.Add(new OracleParameter("u", uid));
|
||||
command.Parameters.Add(new OracleParameter("d", Now17()));
|
||||
var outKey = new OracleParameter("outKey", OracleDbType.Decimal, System.Data.ParameterDirection.Output);
|
||||
command.Parameters.Add(outKey);
|
||||
command.ExecuteNonQuery();
|
||||
key = ((Oracle.ManagedDataAccess.Types.OracleDecimal)outKey.Value!).Value;
|
||||
}
|
||||
catch (OracleException ex) when (ex.Number == 1400)
|
||||
{
|
||||
// 트리거 없음 — MAX+1 명시 채번
|
||||
key = Convert.ToDecimal(Scalar(connection, transaction,
|
||||
"SELECT NVL(MAX(ShtWrdKey),0)+1 FROM E_SHTWRDMST")!);
|
||||
NonQuery(connection, transaction,
|
||||
"INSERT INTO E_SHTWRDMST (ShtWrdKey, ShtCod, ShtParKey, ShtWrdVal, ShtStpLvl, ShtDspSeq, ShtStt, " +
|
||||
"ShtUpdUid, ShtUpdDtf, ShtFstUid, ShtFstDtf) " +
|
||||
"VALUES (:k, :s, 0, :v, 1, :q, 'U', :u, :d, :u, :d)",
|
||||
new OracleParameter("k", key),
|
||||
new OracleParameter("s", shtCod),
|
||||
new OracleParameter("v", value),
|
||||
new OracleParameter("q", seq),
|
||||
new OracleParameter("u", uid),
|
||||
new OracleParameter("d", Now17()));
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
return key;
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>상용구 문구 수정</summary>
|
||||
public void Update(decimal key, string value, string uid)
|
||||
{
|
||||
using var connection = Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.BindByName = true;
|
||||
command.CommandText =
|
||||
"UPDATE E_SHTWRDMST SET ShtWrdVal = :v, ShtUpdUid = :u, ShtUpdDtf = :d WHERE ShtWrdKey = :k";
|
||||
command.Parameters.Add(new OracleParameter("v", value));
|
||||
command.Parameters.Add(new OracleParameter("u", uid));
|
||||
command.Parameters.Add(new OracleParameter("d", Now17()));
|
||||
command.Parameters.Add(new OracleParameter("k", key));
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
/// <summary>상용구 삭제(하위 단계 포함)</summary>
|
||||
public void Remove(decimal key)
|
||||
{
|
||||
using var connection = Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
NonQuery(connection, transaction,
|
||||
"DELETE FROM E_SHTWRDMST WHERE ShtParKey = :k", new OracleParameter("k", key));
|
||||
NonQuery(connection, transaction,
|
||||
"DELETE FROM E_SHTWRDMST WHERE ShtWrdKey = :k", new OracleParameter("k", key));
|
||||
transaction.Commit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>표시 순서 일괄 갱신 — 목록 순서대로 1..N</summary>
|
||||
public void Reorder(IReadOnlyList<decimal> orderedKeys, string uid)
|
||||
{
|
||||
using var connection = Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
for (var i = 0; i < orderedKeys.Count; i++)
|
||||
{
|
||||
NonQuery(connection, transaction,
|
||||
"UPDATE E_SHTWRDMST SET ShtDspSeq = :q, ShtUpdUid = :u, ShtUpdDtf = :d WHERE ShtWrdKey = :k",
|
||||
new OracleParameter("q", (decimal)(i + 1)),
|
||||
new OracleParameter("u", uid),
|
||||
new OracleParameter("d", Now17()),
|
||||
new OracleParameter("k", orderedKeys[i]));
|
||||
}
|
||||
transaction.Commit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private OracleConnection Open()
|
||||
{
|
||||
var connection = new OracleConnection(connectionString);
|
||||
connection.Open();
|
||||
return connection;
|
||||
}
|
||||
|
||||
/// <summary>SHTUPDDTF 17자리(yyyyMMddHHmmssfff)</summary>
|
||||
private static string Now17() => DateTime.Now.ToString("yyyyMMddHHmmssfff");
|
||||
|
||||
private static object? Scalar(OracleConnection connection, OracleTransaction transaction, string sql, params OracleParameter[] parameters)
|
||||
{
|
||||
using var command = connection.CreateCommand();
|
||||
command.Transaction = transaction;
|
||||
command.BindByName = true;
|
||||
command.CommandText = sql;
|
||||
command.Parameters.AddRange(parameters);
|
||||
return command.ExecuteScalar();
|
||||
}
|
||||
|
||||
private static void NonQuery(OracleConnection connection, OracleTransaction transaction, string sql, params OracleParameter[] parameters)
|
||||
{
|
||||
using var command = connection.CreateCommand();
|
||||
command.Transaction = transaction;
|
||||
command.BindByName = true;
|
||||
command.CommandText = sql;
|
||||
command.Parameters.AddRange(parameters);
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using System.Xml;
|
||||
|
||||
namespace SheetMe.Data.Stores;
|
||||
|
||||
/// <summary>E_SctMst 저장 행 1건 — XML 트리 순회 산출물</summary>
|
||||
public sealed class SctRow
|
||||
{
|
||||
/// <summary>컨트롤 이름(SctObjNam)</summary>
|
||||
public string ObjName { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>계층 알파벳 ID(SctObjID) — 15자 우측 공백 패딩</summary>
|
||||
public string ObjId { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>타입 풀네임(SctObjTyp) — AQN 의 첫 콤마 앞</summary>
|
||||
public string ObjType { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>부모 알파벳 ID(SctParObj) — 15자 우측 공백 패딩</summary>
|
||||
public string ParentId { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>표시 순서(SctObjSeq) — DisplaySequence 어트리뷰트</summary>
|
||||
public string ObjSeq { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>텍스트(SctObjTxt) — Text Property 원문</summary>
|
||||
public string ObjText { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 서식 XML → E_SctMst 행 목록 변환 — 레거시 bzSaveSheetDesignNControlInfo.SaveE_SctMst 의 순수 이식.
|
||||
/// 모델이 아닌 '최종 직렬화된 XML'을 순회해 100% 패리티(Spread AsTemplate 특례 포함)를 보장한다.
|
||||
/// </summary>
|
||||
public static class SctMstXmlWalker
|
||||
{
|
||||
#region Methods
|
||||
/// <summary>병합 XML 의 전체 Object 트리를 E_SctMst 행 목록으로 (루트 MDesignerHost 포함 — 레거시 규약)</summary>
|
||||
public static List<SctRow> Walk(string mergedXml)
|
||||
{
|
||||
var rows = new List<SctRow>();
|
||||
var xml = new XmlDocument();
|
||||
xml.LoadXml(mergedXml);
|
||||
|
||||
var objId = "A";
|
||||
foreach (XmlNode node in xml.DocumentElement!.ChildNodes)
|
||||
{
|
||||
if (node.Name.Equals("Object", StringComparison.Ordinal))
|
||||
{
|
||||
AddRow(rows, (XmlElement)node, objId, string.Empty);
|
||||
WalkChildren(rows, (XmlElement)node, objId);
|
||||
}
|
||||
// 레거시 동작 보존: 시퀀스는 노드 종류와 무관하게 진행(Sheet 자식은 Object 뿐이라 동일)
|
||||
objId = NextAlphabetSequence(objId);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/// <summary>알파벳 시퀀스 증가 — A→B…Z→AA→AB…, 전부 Z 면 자릿수 확장(clsCommonLib 이식)</summary>
|
||||
public static string NextAlphabetSequence(string current)
|
||||
{
|
||||
var chars = current.ToCharArray();
|
||||
var index = chars.Length - 1;
|
||||
while (index >= 0 && chars[index] == 'Z')
|
||||
{
|
||||
chars[index] = 'A';
|
||||
index--;
|
||||
}
|
||||
if (index < 0)
|
||||
{
|
||||
return "A" + new string(chars);
|
||||
}
|
||||
chars[index]++;
|
||||
return new string(chars);
|
||||
}
|
||||
|
||||
private static void WalkChildren(List<SctRow> rows, XmlElement parent, string parentObjId)
|
||||
{
|
||||
var childId = "AA";
|
||||
foreach (XmlNode child in parent.ChildNodes)
|
||||
{
|
||||
if (!child.Name.Equals("Object", StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
AddRow(rows, (XmlElement)child, parentObjId + childId, parentObjId);
|
||||
WalkChildren(rows, (XmlElement)child, parentObjId + childId);
|
||||
childId = NextAlphabetSequence(childId);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddRow(List<SctRow> rows, XmlElement node, string objId, string parentId)
|
||||
{
|
||||
var name = node.GetAttribute("name");
|
||||
var typeFullName = node.GetAttribute("type").Split(',')[0];
|
||||
var displaySequence = node.GetAttribute("DisplaySequence");
|
||||
|
||||
var text = string.Empty;
|
||||
string? spreadControlType = null;
|
||||
string? spreadItemList = null;
|
||||
|
||||
foreach (XmlNode child in node.ChildNodes)
|
||||
{
|
||||
if (!child.Name.Equals("Property", StringComparison.OrdinalIgnoreCase) || child is not XmlElement prop)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var propName = prop.GetAttribute("name");
|
||||
if (propName.Equals("Text", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
text = prop.InnerText;
|
||||
}
|
||||
else if (propName.Equals("ControlType", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
spreadControlType = prop.InnerText;
|
||||
}
|
||||
else if (propName.Equals("ControlTypeAsTemplate_ItemLIST", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
spreadItemList = prop.InnerText;
|
||||
}
|
||||
}
|
||||
|
||||
rows.Add(new SctRow
|
||||
{
|
||||
ObjName = name,
|
||||
ObjId = objId.PadRight(15),
|
||||
ObjType = typeFullName,
|
||||
ParentId = parentId.PadRight(15),
|
||||
ObjSeq = displaySequence,
|
||||
ObjText = text,
|
||||
});
|
||||
|
||||
// Spread AsTemplate 특례 — 항목 목록을 개별 행으로(레거시 이식, Chr(5) 구분)
|
||||
if (string.Equals(typeFullName, "M.EMR.UserControl.Spread", StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(spreadControlType, "AsTemplate", StringComparison.OrdinalIgnoreCase)
|
||||
&& spreadItemList is not null)
|
||||
{
|
||||
var items = spreadItemList.Split((char)5);
|
||||
for (var i = 0; i < items.Length - 1; i++)
|
||||
{
|
||||
rows.Add(new SctRow
|
||||
{
|
||||
ObjName = items[i],
|
||||
ObjId = i.ToString(),
|
||||
ObjType = "ItemOfSpreadAsTemplate",
|
||||
ParentId = string.Empty,
|
||||
ObjSeq = string.Empty,
|
||||
ObjText = i.ToString().PadLeft(3, '0') + "_" + items[i],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Text;
|
||||
using SheetMe.Core.Models;
|
||||
using SheetMe.Core.Serialization;
|
||||
|
||||
namespace SheetMe.Data.Stores;
|
||||
|
||||
/// <summary>
|
||||
/// 파일 기반 서식 저장소 — 레거시 XML(.xml) 파일 열기/저장.
|
||||
/// 레거시 디자이너의 XML 내보내기(TK_ExportXML) 산출물과 호환되며, DB 없는 개발 모드의 기본 저장소.
|
||||
/// </summary>
|
||||
public sealed class XmlFileFormStore
|
||||
{
|
||||
#region Member Fields
|
||||
private readonly LegacyXmlSerializer serializer = new();
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
/// <summary>XML 파일 열기 — FormId 는 파일명에서 유래</summary>
|
||||
public FormDocument LoadFile(string path)
|
||||
{
|
||||
var content = File.ReadAllText(path);
|
||||
var document = serializer.Read(content);
|
||||
document.FormId = Path.GetFileNameWithoutExtension(path);
|
||||
document.Title = document.FormId;
|
||||
return document;
|
||||
}
|
||||
|
||||
/// <summary>XML 파일 저장 — UTF-8(BOM 없음)</summary>
|
||||
public void SaveFile(FormDocument document, string path)
|
||||
{
|
||||
var xml = serializer.Write(document);
|
||||
File.WriteAllText(path, xml, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user