'수정'을 되살린다 — 지금 쓰이는 버전을 제자리에서 고친다
레거시 TK_MODIFY('기록지 수정'). SheetMe 에는 저장만 있었고 이것이 없었다.
DbSmoke.cs:1072 가 그 부재를 이미 적어 뒀다 — 실측 ShtCneYon='Y' 40건, 현역 19건.
<b>먼저 내 앞선 설명을 고친다.</b> 나는 수정이 ShtCneYon 제자리 갱신 경로라고 했는데 틀렸다.
수정 경로는 ShtCneYon 을 <b>읽지도 않는다</b>. 그 컬럼은 저장이 버저닝이냐 제자리냐만 가른다.
저장과 수정은 이력 방향이 반대다.
저장: 옛 행을 SdgDelYon='Y' 로 내리고 <b>새 행이 활성</b>이 된다(SdgKey 바뀜)
수정: <b>활성 행을 제자리에서 갱신</b>하고 옛 XML 을 담은 새 행이 이력이 된다(SdgKey 유지)
SdgKey 가 유지되므로 EMR 이 참조하던 키가 그대로다 — 그게 이 동작의 존재 이유다.
E_SctMst 를 건드리지 않는다(레거시 :170-174 주석 처리). 그것이 "글자만 수정 가능"이라는
경고문의 기술적 근거다 — 기존 매핑 행은 SdgKey 가 그대로라 유효하지만 새 컨트롤은 행이 안 생긴다.
<b>레거시 버그 둘은 복제하지 않는다.</b>
① 감사 컬럼이 뒤집혀 있었다 — 활성 행에는 아무것도 안 쓰고 <b>옛 디자인을 담은 이력 행</b>이
현재 사용자·시각을 받았다(:162-163). 이력 패널이 "누가 언제 이 버전을 만들었는가"를 거꾸로 보여 준다.
활성 행에 기록하고 이력 행은 원래 값을 지킨다.
② Rows(0) 을 개수 검사보다 먼저 읽어 활성 행이 없으면 IndexOutOfRange 였다(:150).
먼저 확인하고 "수정 대신 저장을 쓰세요"로 안내한다.
<b>새 컨트롤은 막는다.</b> 레거시는 산문으로만 경고하고 막지 않았다 —
경고문을 읽지 않으면 그대로 번진다. SctMstXmlWalker 로 문서의 컨트롤 이름을 뽑아
활성 SdgKey 의 E_SctMst 행과 대조한다. 판정 근거를 저장이 행을 만드는 규칙과 같은 것으로 통일해서,
"행이 원래 안 생기는 종류"를 새 컨트롤로 오인하지 않는다.
<b>권한 게이트는 유지한다.</b> 레거시가 이 버튼만 전산실에 묶어 둔 이유가 위와 같다
(frmSheetDesigner.vb:1029-1060, 주석 '전산실만 사용가능'). 저장 버튼에는 그런 분기가 없다.
규칙을 Core 의 순수 함수로 옮겨 표로 고정했다 — 병원·부서 조합이 세 갈래라
화면에서 즉석 판정하면 어느 갈래가 왜 막혔는지 확인할 방법이 없다.
막힌 이유도 갈래마다 다르게 말한다("권한 없음"만으로는 누구에게 물어야 할지 모른다).
<b>HspStrDte 는 세션에 없다.</b> 레거시는 그 값으로 "2025-05-01 이후 개원 병원은 사내 계정만"을 가른다.
빈 문자열로 두면 그 갈래가 안 걸리고 부서 규칙(EDPS)으로 떨어진다 — 레거시의 다수 경로와 같다.
없는 값을 지어내 더 조이지 않고, 이 사실을 코드와 테스트에 적어 뒀다.
게이트: 테스트 298/298(신규 7), --edit-smoke 0실패, --dialog-shots FAIL 0 대조군 4/4,
--modal-check 0실패, --cleartype 11/11, --maxrect 0실패, --scale-budget 5/5, 빌드 경고 0,
--db-smoke 1271건 diff 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일.
<b>실DB 수정 경로는 아직 안 돌려 봤다</b> — 운영 테이블에 쓰는 동작이라 --db-* 진단으로
왕복 검증을 붙이는 것이 다음 일이다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a386c5504b
commit
ae2f8f2f61
@@ -286,6 +286,134 @@ public sealed class OracleLegacyFormStore
|
||||
/// 디자인 저장 — 레거시 동일 의미론(제자리 갱신 vs 버저닝), 단일 트랜잭션.
|
||||
/// 반환: 저장된 SdgKey.
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// 지금 쓰이는 버전을 <b>제자리에서</b> 정정한다 — 레거시 <c>TK_MODIFY</c>(3-인자 오버로드).
|
||||
///
|
||||
/// <b>저장과 이력 방향이 반대다.</b> 저장은 옛 행을 <c>SdgDelYon='Y'</c> 로 내리고 새 행이 활성이 된다.
|
||||
/// 수정은 <b>활성 행이 제자리에서 갱신되고, 옛 XML 을 담은 새 행이 이력</b>으로 들어간다.
|
||||
/// 그래서 <c>SdgKey</c> 가 바뀌지 않는다 — EMR 이 참조하던 키가 그대로 유지된다.
|
||||
///
|
||||
/// <b><c>E_SctMst</c> 를 건드리지 않는다.</b> 레거시가 그 부분을 주석 처리해 두었고
|
||||
/// (<c>bzSaveSheetDesignNControlInfo.vb:170-174</c>), 그것이 "글자만 수정 가능"이라는 경고문의
|
||||
/// 기술적 근거다. SdgKey 가 그대로라 기존 매핑 행은 유효하지만 <b>새 컨트롤은 매핑 행이 안 생긴다</b>.
|
||||
/// 호출부가 <see cref="NewControlNames"/> 로 먼저 막는다.
|
||||
///
|
||||
/// <b>레거시 버그 둘은 복제하지 않는다.</b>
|
||||
/// ① 감사 컬럼이 뒤집혀 있었다 — 활성 행에는 아무것도 안 쓰고
|
||||
/// <b>옛 디자인을 담은 이력 행</b>이 현재 사용자·시각을 받았다(<c>:162-163</c>).
|
||||
/// 그러면 이력 패널이 "누가 언제 이 버전을 만들었는가"를 거꾸로 보여 준다.
|
||||
/// 여기서는 활성 행에 기록하고, 이력 행은 원래 값을 지킨다.
|
||||
/// ② <c>Rows(0)</c> 을 개수 검사보다 먼저 읽어 활성 행이 없으면 예외였다(<c>:150</c>).
|
||||
/// 여기서는 먼저 확인하고 뜻이 통하는 오류를 낸다.
|
||||
/// </summary>
|
||||
public decimal ModifyDesign(FormDocument document, string uid)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(document.FormId))
|
||||
{
|
||||
throw new InvalidOperationException("서식 코드(FormId)가 없습니다.");
|
||||
}
|
||||
var mergedXml = serializer.Write(document);
|
||||
|
||||
using var connection = Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
var now = ServerClock.Read(connection, transaction).Minute12;
|
||||
var activeSdgKey = ResolveActiveSdgKey(connection, transaction, document.FormId);
|
||||
if (activeSdgKey <= 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"서식 [{document.FormId}] 에 지금 쓰이는 디자인이 없습니다. 수정 대신 저장을 쓰세요.");
|
||||
}
|
||||
|
||||
// 옛 디자인과 그 감사 값을 먼저 떠 둔다 — 이력 행이 그 값을 그대로 가져가야
|
||||
// "그 버전을 누가 언제 만들었는지"가 보존된다.
|
||||
string oldXml = string.Empty, oldUid = uid, oldStamp = now;
|
||||
using (var read = connection.CreateCommand())
|
||||
{
|
||||
read.Transaction = transaction;
|
||||
read.BindByName = true;
|
||||
read.CommandText =
|
||||
"SELECT SdgDesign, NVL(SdgUidCod,' '), NVL(SdgUpdDtm,' ') FROM E_SdgMst WHERE SdgKey = :k";
|
||||
read.Parameters.Add(new OracleParameter("k", activeSdgKey));
|
||||
using var reader = read.ExecuteReader();
|
||||
if (reader.Read())
|
||||
{
|
||||
oldXml = reader.IsDBNull(0) ? string.Empty : reader.GetString(0);
|
||||
oldUid = reader.GetString(1).Trim();
|
||||
oldStamp = reader.GetString(2).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
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", activeSdgKey));
|
||||
|
||||
var historyKey = Convert.ToDecimal(Scalar(connection, transaction,
|
||||
"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, 'Y', '', '')",
|
||||
new OracleParameter("k", historyKey),
|
||||
new OracleParameter("s", document.FormId),
|
||||
new OracleParameter("d", OracleDbType.Clob) { Value = oldXml },
|
||||
new OracleParameter("u", oldUid.Length > 0 ? oldUid : uid),
|
||||
new OracleParameter("t", oldStamp.Length > 0 ? oldStamp : now));
|
||||
|
||||
transaction.Commit();
|
||||
return activeSdgKey;
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 문서에 있는데 지금 활성 버전의 <c>E_SctMst</c> 에는 없는 컨트롤 이름들.
|
||||
///
|
||||
/// 수정은 <c>E_SctMst</c> 를 안 쓰므로 이 이름들은 <b>매핑 행 없이</b> 저장되고
|
||||
/// EMR 에서 조용히 값이 안 채워진다. 그래서 호출부가 수정을 막는 근거로 쓴다.
|
||||
/// 판정 근거를 <see cref="SctMstXmlWalker"/> 로 통일한다 — 저장이 행을 만드는 규칙과 같은 것이라
|
||||
/// "행이 원래 안 생기는 종류"를 새 컨트롤로 오인하지 않는다.
|
||||
/// </summary>
|
||||
public List<string> NewControlNames(FormDocument document)
|
||||
{
|
||||
var names = new List<string>();
|
||||
using var connection = Open();
|
||||
var activeSdgKey = ResolveActiveSdgKey(connection, null, document.FormId);
|
||||
if (activeSdgKey <= 0)
|
||||
{
|
||||
return names;
|
||||
}
|
||||
var existing = new HashSet<string>(StringComparer.Ordinal);
|
||||
using (var query = connection.CreateCommand())
|
||||
{
|
||||
query.BindByName = true;
|
||||
query.CommandText = "SELECT SctObjNam FROM E_SctMst WHERE SctSdgKey = :k";
|
||||
query.Parameters.Add(new OracleParameter("k", activeSdgKey));
|
||||
using var reader = query.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
existing.Add(reader.GetString(0));
|
||||
}
|
||||
}
|
||||
foreach (var row in SctMstXmlWalker.Walk(serializer.Write(document)))
|
||||
{
|
||||
if (!string.Equals(row.ObjType, SpreadItemType, StringComparison.OrdinalIgnoreCase)
|
||||
&& !existing.Contains(row.ObjName) && !names.Contains(row.ObjName))
|
||||
{
|
||||
names.Add(row.ObjName);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
public decimal SaveDesign(FormDocument document, string uid)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(document.FormId))
|
||||
|
||||
Reference in New Issue
Block a user