바이탈 17종 + 주치의 1종 + _Refer 사유 정정 — 76종 → 95종
## 바이탈(측정치) 17종
E_EmdInf_VITAL ⨝ E_EmrInf(삭제 제외) 를 내원으로 거르고 해당 컬럼이 빈 값이 아닌
행을 측정일시 순으로 한 건 집는다 — 무인자는 첫 측정(ASC), _LAST 는 마지막(DESC).
키/몸무게/체온/맥박/호흡/SPO2 (±LAST) · 혈압/혈압_LAST(BPS||'/'||BPD) ·
혈압_BPS/_BPD(단독값이지만 널 필터는 둘 다 — 레거시 그대로) · VITAL접수일시_LAST(HH:MM).
태그별 (식·컬럼·필터·정렬)을 전부 본문에서 확인해 표로 박았다(:11508-13046).
조회는 태그가 물을 때 한 값씩, 태그별 1회 캐시.
## 머리둘레 2종은 레거시가 고장이다
SELECT 는 EmdHc AS HC 인데 Item("BP") 를 읽는다(:11744, 12581) —
행이 있으면 예외 → MessageBox → "". 레거시에서 한 번도 값이 나온 적 없는 태그다.
그대로 빈 값 + "레거시 결함(컬럼명 불일치)으로 항상 빈 값이던 태그" 사유로 둔다.
고쳐서 값을 내면 레거시와 달라진다 — 고칠지는 별도 결정.
## 실DB 확인
--db-patient ㉙ 신규: 내원 6004487 에서 첫/마지막 몸무게 2자.
표 부재 가능성도 수술과 같은 방식으로 가른다(ORA-00942 명시).
## 게이트
- dotnet test 338/338 · --edit-smoke 실패 0(이름 검사 95종)
- --db-patient ①~㉙ 전건 통과 · --db-render P062 md5 불변
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3e9d5b5cf3
commit
94bdcc763f
@@ -0,0 +1,64 @@
|
|||||||
|
using Oracle.ManagedDataAccess.Client;
|
||||||
|
|
||||||
|
namespace SheetMe.Data.Stores;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 바이탈(측정치) 조회 — 레거시 <c>OCM_키/몸무게/체온/혈압/맥박/호흡/SPO2(±_LAST)</c> 계열
|
||||||
|
/// (bzDataInterface.vb:11508-13046)의 공통 조회.
|
||||||
|
///
|
||||||
|
/// 전부 같은 틀이다:
|
||||||
|
/// <c>E_EmdInf_VITAL ⨝ E_EmrInf(EmrKey=EmdEmrKey, 삭제 제외)</c> 를 내원으로 거르고
|
||||||
|
/// 해당 컬럼이 비어 있지 않은 행을 측정일시 순으로 한 건 집는다 —
|
||||||
|
/// 무인자는 <b>첫 측정</b>(ASC), _LAST 는 <b>마지막 측정</b>(DESC).
|
||||||
|
///
|
||||||
|
/// SELECT 식·널 필터·정렬까지 태그마다 본문에서 확인한 대로 호출부(해석기)가 지정한다.
|
||||||
|
/// 식과 컬럼명은 전부 <b>코드 상수</b>에서만 온다 — 사용자 입력이 닿는 경로가 아니다.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class VitalStore
|
||||||
|
{
|
||||||
|
#region Member Fields
|
||||||
|
private readonly string connectionString;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
public VitalStore(string connectionString) => this.connectionString = connectionString;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
/// <summary>측정치 한 건 — 없으면 빈 문자열</summary>
|
||||||
|
/// <param name="selectExpr">SELECT 식(별칭 포함) — 예: "EmdWeight" / "EmdBps || '/' || EmdBpd AS BP"</param>
|
||||||
|
/// <param name="pick">결과에서 집을 컬럼(별칭)</param>
|
||||||
|
/// <param name="notNullColumns">비어 있으면 안 되는 컬럼들(레거시 필터 그대로)</param>
|
||||||
|
/// <param name="orderBy">정렬 절 — 예: "EmdDte ASC, EmdTime ASC"</param>
|
||||||
|
public string One(decimal comNum, string selectExpr, string pick,
|
||||||
|
string[] notNullColumns, string orderBy)
|
||||||
|
{
|
||||||
|
var filters = string.Concat(notNullColumns.Select(c =>
|
||||||
|
$" AND ({c} IS NOT NULL AND {c} <> ' ') "));
|
||||||
|
var sql =
|
||||||
|
"SELECT * FROM ("
|
||||||
|
+ $" SELECT {selectExpr}"
|
||||||
|
+ " FROM E_EmdInf_VITAL"
|
||||||
|
+ " INNER JOIN E_EmrInf ON EmrKey = EmdEmrKey"
|
||||||
|
+ " AND (EmrSttFlg IS NULL OR EmrSttFlg <> 'D')"
|
||||||
|
+ " WHERE EmrComNum = :iComNum"
|
||||||
|
+ filters
|
||||||
|
+ $" ORDER BY {orderBy}"
|
||||||
|
+ " ) WHERE ROWNUM = 1";
|
||||||
|
|
||||||
|
using var connection = new OracleConnection(connectionString);
|
||||||
|
connection.Open();
|
||||||
|
using var command = connection.CreateCommand();
|
||||||
|
command.BindByName = true;
|
||||||
|
command.CommandText = sql;
|
||||||
|
command.Parameters.Add(new OracleParameter("iComNum", comNum));
|
||||||
|
using var reader = command.ExecuteReader();
|
||||||
|
if (!reader.Read())
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
var at = reader.GetOrdinal(pick);
|
||||||
|
return reader.IsDBNull(at) ? string.Empty : reader.GetValue(at).ToString()!.Trim();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -2264,6 +2264,33 @@ public static class DbSmoke
|
|||||||
Check("㉘ 수술 쿼리가 행을 돌려준다", surgeries.Count > 0,
|
Check("㉘ 수술 쿼리가 행을 돌려준다", surgeries.Count > 0,
|
||||||
"OprStt='E' 조건이나 조인을 의심할 것");
|
"OprStt='E' 조건이나 조인을 의심할 것");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ㉙ 바이탈 — E_EmdInf_VITAL 에 행을 가진 내원으로 실행 확인
|
||||||
|
var vitOwner = FirstComNum(conn,
|
||||||
|
"E_EmdInf_VITAL INNER JOIN E_EmrInf ON EmrKey = EmdEmrKey",
|
||||||
|
"EmrComNum", "EmrComNum");
|
||||||
|
if (vitOwner is null)
|
||||||
|
{
|
||||||
|
foreach (var line in ExplainPeriod(conn, 0,
|
||||||
|
"SELECT COUNT(*) VITCNT FROM E_EmdInf_VITAL WHERE :k = 0"))
|
||||||
|
{
|
||||||
|
lines.Add($" [진단] E_EmdInf_VITAL: {line}");
|
||||||
|
}
|
||||||
|
lines.Add("SKIP ㉙ 바이탈 — 확인 불가. 위 [진단] 줄이 원인이다"
|
||||||
|
+ " (ORA-00942 면 이 시험 DB 에 표가 없어 운영 DB 에서만 검증 가능)");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var vital = new VitalStore(conn);
|
||||||
|
var weight = vital.One(vitOwner.Value, "EmdWeight", "EmdWeight",
|
||||||
|
new[] { "EmdWeight" }, "EmdDte ASC, EmdTime ASC");
|
||||||
|
var weightLast = vital.One(vitOwner.Value, "EmdWeight", "EmdWeight",
|
||||||
|
new[] { "EmdWeight" }, "EmdDte DESC, EmdTime DESC");
|
||||||
|
lines.Add($" 바이탈: 내원 {vitOwner.Value:F0} → 첫 몸무게 {weight.Length}자,"
|
||||||
|
+ $" 마지막 {weightLast.Length}자 (값은 기록하지 않는다)");
|
||||||
|
Check("㉙ 바이탈 조회가 값을 돌려준다", weight.Length > 0,
|
||||||
|
"EmdWeight 널 필터나 조인을 의심할 것");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,6 +152,15 @@ public static class PatientIdentity
|
|||||||
: new ReviewDiagnosisStore(connection).Diagnoses(context.ComNum, scope);
|
: new ReviewDiagnosisStore(connection).Diagnoses(context.ComNum, scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>바이탈 공급자 — 태그가 물을 때 한 값씩 읽는다(측정 태그 없는 서식이 다수다)</summary>
|
||||||
|
public static Func<string, string, string[], string, string> VitalSourceFor(PatientContext context)
|
||||||
|
{
|
||||||
|
var connection = ConfigService.Current.ConnectionString;
|
||||||
|
return (expr, pick, notNull, order) => connection.Length == 0
|
||||||
|
? string.Empty
|
||||||
|
: new VitalStore(connection).One(context.ComNum, expr, pick, notNull, order);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>수술 행 공급자 — 상병과 같은 정책(필요할 때 1회, 실패는 던져 사유로)</summary>
|
/// <summary>수술 행 공급자 — 상병과 같은 정책(필요할 때 1회, 실패는 던져 사유로)</summary>
|
||||||
public static Func<IReadOnlyList<Dictionary<string, string>>> SurgerySourceFor(PatientContext context)
|
public static Func<IReadOnlyList<Dictionary<string, string>>> SurgerySourceFor(PatientContext context)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ public static class PatientSession
|
|||||||
PatientIdentity.HospitalRowOf(picked),
|
PatientIdentity.HospitalRowOf(picked),
|
||||||
PatientIdentity.DiagnosisSourceFor(picked),
|
PatientIdentity.DiagnosisSourceFor(picked),
|
||||||
PatientIdentity.SurgerySourceFor(picked),
|
PatientIdentity.SurgerySourceFor(picked),
|
||||||
PatientIdentity.ReviewDiagnosisSourceFor(picked))
|
PatientIdentity.ReviewDiagnosisSourceFor(picked),
|
||||||
|
PatientIdentity.VitalSourceFor(picked))
|
||||||
: session;
|
: session;
|
||||||
return (tags, new MDataTableRunner(document, Variables()));
|
return (tags, new MDataTableRunner(document, Variables()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,6 +185,11 @@ public sealed class PatientTagResolver : ITagValueResolver
|
|||||||
/// <summary>심사 상병(B_OkdInf) 공급자 — 상병과 같은 정책</summary>
|
/// <summary>심사 상병(B_OkdInf) 공급자 — 상병과 같은 정책</summary>
|
||||||
private readonly Func<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>>? reviewSource;
|
private readonly Func<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>>? reviewSource;
|
||||||
|
|
||||||
|
/// <summary>바이탈 공급자 — (식, 컬럼, 필터, 정렬) 로 한 값을 읽는다. 태그별 1회 캐시</summary>
|
||||||
|
private readonly Func<string, string, string[], string, string>? vitalSource;
|
||||||
|
|
||||||
|
private readonly Dictionary<string, string> vitalCache = new(StringComparer.Ordinal);
|
||||||
|
|
||||||
private readonly Dictionary<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>> reviewCache = new();
|
private readonly Dictionary<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>> reviewCache = new();
|
||||||
|
|
||||||
public PatientTagResolver(PatientContext context, ITagValueResolver next,
|
public PatientTagResolver(PatientContext context, ITagValueResolver next,
|
||||||
@@ -195,7 +200,8 @@ public sealed class PatientTagResolver : ITagValueResolver
|
|||||||
IReadOnlyDictionary<string, string>? hospital = null,
|
IReadOnlyDictionary<string, string>? hospital = null,
|
||||||
Func<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>>? diagnosisSource = null,
|
Func<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>>? diagnosisSource = null,
|
||||||
Func<IReadOnlyList<Dictionary<string, string>>>? surgerySource = null,
|
Func<IReadOnlyList<Dictionary<string, string>>>? surgerySource = null,
|
||||||
Func<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>>? reviewSource = null)
|
Func<DiagnosisStore.Scope, IReadOnlyList<Dictionary<string, string>>>? reviewSource = null,
|
||||||
|
Func<string, string, string[], string, string>? vitalSource = null)
|
||||||
{
|
{
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.next = next;
|
this.next = next;
|
||||||
@@ -208,6 +214,7 @@ public sealed class PatientTagResolver : ITagValueResolver
|
|||||||
this.diagnosisSource = diagnosisSource;
|
this.diagnosisSource = diagnosisSource;
|
||||||
this.surgerySource = surgerySource;
|
this.surgerySource = surgerySource;
|
||||||
this.reviewSource = reviewSource;
|
this.reviewSource = reviewSource;
|
||||||
|
this.vitalSource = vitalSource;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -243,6 +250,10 @@ public sealed class PatientTagResolver : ITagValueResolver
|
|||||||
{
|
{
|
||||||
return reviewValue;
|
return reviewValue;
|
||||||
}
|
}
|
||||||
|
if (FromVital(tag) is { } vitalValue)
|
||||||
|
{
|
||||||
|
return vitalValue;
|
||||||
|
}
|
||||||
if (Computed.TryGetValue(tag, out var compute))
|
if (Computed.TryGetValue(tag, out var compute))
|
||||||
{
|
{
|
||||||
var made = compute(context);
|
var made = compute(context);
|
||||||
@@ -703,6 +714,72 @@ public sealed class PatientTagResolver : ITagValueResolver
|
|||||||
: new TagValue(false, "심사 상병 행은 있는데 그 값이 비어 있습니다");
|
: new TagValue(false, "심사 상병 행은 있는데 그 값이 비어 있습니다");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 바이탈 17종 — 태그별 (SELECT 식, 집는 컬럼, 널 필터, 정렬).
|
||||||
|
/// 전부 본문에서 확인(bzDataInterface.vb:11508-13046). 무인자 = 첫 측정(ASC), _LAST = 마지막(DESC).
|
||||||
|
/// 혈압 계열은 BPS·BPD <b>둘 다</b> 비어 있지 않아야 한다(레거시 필터 그대로 — _BPS 단독도 둘 다 건다).
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Dictionary<string, (string Expr, string Pick, string[] NotNull, string Order)> Vitals =
|
||||||
|
new(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
["OCM_키"] = ("EmdHeight", "EmdHeight", new[] { "EmdHeight" }, "EmdDte ASC, EmdTime ASC"),
|
||||||
|
["OCM_키_LAST"] = ("EmdHeight", "EmdHeight", new[] { "EmdHeight" }, "EmdDte DESC, EmdTime DESC"),
|
||||||
|
["OCM_몸무게"] = ("EmdWeight", "EmdWeight", new[] { "EmdWeight" }, "EmdDte ASC, EmdTime ASC"),
|
||||||
|
["OCM_몸무게_LAST"] = ("EmdWeight", "EmdWeight", new[] { "EmdWeight" }, "EmdDte DESC, EmdTime DESC"),
|
||||||
|
["OCM_체온"] = ("EmdTemp", "EmdTemp", new[] { "EmdTemp" }, "EmdDte ASC, EmdTime ASC"),
|
||||||
|
["OCM_체온_LAST"] = ("EmdTemp", "EmdTemp", new[] { "EmdTemp" }, "EmdDte DESC, EmdTime DESC"),
|
||||||
|
["OCM_맥박"] = ("EmdPuls", "EmdPuls", new[] { "EmdPuls" }, "EmdDte ASC, EmdTime ASC"),
|
||||||
|
["OCM_맥박_LAST"] = ("EmdPuls", "EmdPuls", new[] { "EmdPuls" }, "EmdDte DESC, EmdTime DESC"),
|
||||||
|
["OCM_호흡"] = ("EmdResp", "EmdResp", new[] { "EmdResp" }, "EmdDte ASC, EmdTime ASC"),
|
||||||
|
["OCM_호흡_LAST"] = ("EmdResp", "EmdResp", new[] { "EmdResp" }, "EmdDte DESC, EmdTime DESC"),
|
||||||
|
["OCM_SPO2"] = ("EmdSpo", "EmdSpo", new[] { "EmdSpo" }, "EmdDte ASC, EmdTime ASC"),
|
||||||
|
["OCM_SPO2_LAST"] = ("EmdSpo", "EmdSpo", new[] { "EmdSpo" }, "EmdDte DESC, EmdTime DESC"),
|
||||||
|
["OCM_혈압"] = ("EmdBps || '/' || EmdBpd AS BP", "BP",
|
||||||
|
new[] { "EmdBps", "EmdBpd" }, "EmdDte ASC, EmdTime ASC"),
|
||||||
|
["OCM_혈압_LAST"] = ("EmdBps || '/' || EmdBpd AS BP", "BP",
|
||||||
|
new[] { "EmdBps", "EmdBpd" }, "EmdDte DESC, EmdTime DESC"),
|
||||||
|
["OCM_혈압_BPS"] = ("EmdBps AS BP", "BP", new[] { "EmdBps", "EmdBpd" }, "EmdDte ASC, EmdTime ASC"),
|
||||||
|
["OCM_혈압_BPD"] = ("EmdBpd AS BP", "BP", new[] { "EmdBps", "EmdBpd" }, "EmdDte ASC, EmdTime ASC"),
|
||||||
|
["OCM_VITAL접수일시_LAST"] = (
|
||||||
|
"SUBSTR(EmrAdpTim,1,2) || ':' || SUBSTR(EmrAdpTim,3,2) AS EmrAdpTim", "EmrAdpTim",
|
||||||
|
new[] { "EmrAdpTim" }, "EmrAdpTim DESC"),
|
||||||
|
};
|
||||||
|
|
||||||
|
private TagValue? FromVital(string tag)
|
||||||
|
{
|
||||||
|
// 머리둘레 두 종은 레거시 결함이다 — SELECT 는 EmdHc AS HC 인데 Item("BP") 를 읽어
|
||||||
|
// 행이 있으면 예외 → MessageBox → "" 다(bzDataInterface.vb:11744, 12581).
|
||||||
|
// 즉 레거시에서 <b>한 번도 값이 나온 적 없는</b> 태그다. 그대로 빈 값 + 사유로 둔다.
|
||||||
|
if (tag is "OCM_머리둘레" or "OCM_머리둘레_LAST")
|
||||||
|
{
|
||||||
|
return new TagValue(false,
|
||||||
|
"레거시 결함(컬럼명 불일치)으로 항상 빈 값이던 태그입니다");
|
||||||
|
}
|
||||||
|
if (!Vitals.TryGetValue(tag, out var spec))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (vitalSource is null)
|
||||||
|
{
|
||||||
|
return new TagValue(false, "바이탈 조회가 연결되지 않았습니다");
|
||||||
|
}
|
||||||
|
if (!vitalCache.TryGetValue(tag, out var value))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
value = vitalSource(spec.Expr, spec.Pick, spec.NotNull, spec.Order);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new TagValue(false, $"바이탈 조회에 실패했습니다: {ex.GetType().Name}");
|
||||||
|
}
|
||||||
|
vitalCache[tag] = value;
|
||||||
|
}
|
||||||
|
return value.Length > 0
|
||||||
|
? new TagValue(true, value)
|
||||||
|
: new TagValue(false, "이 내원에 해당 측정 기록이 없습니다");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>주민번호에서 나온 값 — 비면 <b>왜</b> 비었는지 말한다</summary>
|
/// <summary>주민번호에서 나온 값 — 비면 <b>왜</b> 비었는지 말한다</summary>
|
||||||
private TagValue Wrap(string value) => value.Length > 0
|
private TagValue Wrap(string value) => value.Length > 0
|
||||||
? new TagValue(true, value)
|
? new TagValue(true, value)
|
||||||
@@ -732,6 +809,10 @@ public sealed class PatientTagResolver : ITagValueResolver
|
|||||||
"OCM_OPNAME", "OCM_OPRCODNAM", "OCM_DX", "OCM_수술상병", "OCM_수술부위",
|
"OCM_OPNAME", "OCM_OPRCODNAM", "OCM_DX", "OCM_수술상병", "OCM_수술부위",
|
||||||
"OCM_수술_OprPatETC", "OCM_수술집도의", "OCM_수술마취의사",
|
"OCM_수술_OprPatETC", "OCM_수술집도의", "OCM_수술마취의사",
|
||||||
"OCM_심사_주상병명", "OCM_심사_주상병코드", "OCM_심사_부상병명", "OCM_심사_부상병코드",
|
"OCM_심사_주상병명", "OCM_심사_주상병코드", "OCM_심사_부상병명", "OCM_심사_부상병코드",
|
||||||
|
"OCM_키", "OCM_키_LAST", "OCM_몸무게", "OCM_몸무게_LAST", "OCM_체온", "OCM_체온_LAST",
|
||||||
|
"OCM_맥박", "OCM_맥박_LAST", "OCM_호흡", "OCM_호흡_LAST", "OCM_SPO2", "OCM_SPO2_LAST",
|
||||||
|
"OCM_혈압", "OCM_혈압_LAST", "OCM_혈압_BPS", "OCM_혈압_BPD", "OCM_VITAL접수일시_LAST",
|
||||||
|
"OCM_머리둘레", "OCM_머리둘레_LAST",
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -228,7 +228,8 @@ public partial class PreviewWindow : Window
|
|||||||
PatientIdentity.HospitalRowOf(picked),
|
PatientIdentity.HospitalRowOf(picked),
|
||||||
PatientIdentity.DiagnosisSourceFor(picked),
|
PatientIdentity.DiagnosisSourceFor(picked),
|
||||||
PatientIdentity.SurgerySourceFor(picked),
|
PatientIdentity.SurgerySourceFor(picked),
|
||||||
PatientIdentity.ReviewDiagnosisSourceFor(picked));
|
PatientIdentity.ReviewDiagnosisSourceFor(picked),
|
||||||
|
PatientIdentity.VitalSourceFor(picked));
|
||||||
// 러너를 <b>새로</b> 만든다. 같은 러너를 재사용하면 옛 환자의 조회 결과가 캐시에 남아
|
// 러너를 <b>새로</b> 만든다. 같은 러너를 재사용하면 옛 환자의 조회 결과가 캐시에 남아
|
||||||
// 환자를 바꿨는데 표는 그대로가 된다 — 태그만 바뀌고 표는 안 바뀌면 아무도 눈치채지 못한다.
|
// 환자를 바꿨는데 표는 그대로가 된다 — 태그만 바뀌고 표는 안 바뀌면 아무도 눈치채지 못한다.
|
||||||
fields = new MDataTableRunner(designer.Document,
|
fields = new MDataTableRunner(designer.Document,
|
||||||
|
|||||||
Reference in New Issue
Block a user