HIS 사용자 컨텍스트 + 서식생성기 권한 게이트 + 서버 시각 전환
[사용자 컨텍스트] 감사 컬럼에 Windows 계정명이 들어가던 6지점을 HIS UidCod 로 교체했다. 레거시 이력 화면(ucSheetHistory.vb:51)이 SdgUidCod 를 수정자로 표시하므로 지금까지는 감사 추적이 끊겨 있었다. - UserContextStore — M_UidMst 단독 조회로 인증 판정(레거시 bzLogIn 자동로그인과 동일 의미론, 비밀번호를 보지 않고 행 존재 + 유효기간만 확인). 부서/병원은 LEFT JOIN best-effort 표시용. 적용일자는 서버 시각을 쓴다 — 단말 시계가 밀리면 유효 계정이 거부될 수 있다. - UserSession — 정적 set-once. 생성자 주입을 택하지 않은 이유는 진단 모드(--db-*)가 ViewModel 그래프 밖이라 주입으로는 감사 기록 지점에 닿을 수 없기 때문이다. 대신 읽는 곳을 App 초기화 / FormDesignDataBusiness / MainViewModel 표시 세 군데로 제한했고, 저장소는 지금처럼 uid 를 파라미터로만 받는다. 진단 모드는 "SMOKE" 식별자를 쓴다. - StartupArguments — 레거시 규약 "UidCod,ComNum,ShtCod" 관용 파싱(PadRight 후 split) 이식. ComNum 은 레거시에서도 선언·대입뿐인 죽은 인자라 보존만 하고 쓰지 않는다. - App.OnStartup 재구성 — 진단 플래그 12종을 RunDiagnostic 으로 분리(각 분기가 종료 코드를 반환하도록 정리), 세션 확정 후 MainView 표시. 기동 인자에 ShtCod 가 있으면 PendingSheetCode 로 넘겨 빈 새 문서를 만들지 않고 그 서식만 연다(열기 실패 시에는 빈 문서로 폴백). - 인자로 받은 UidCod 조회에 실패하면 기동을 중단한다(레거시 :892 와 동형). 인자 없이 단독 실행하면 미인증으로 계속하되 DB 쓰기를 차단하고, 개발 편의는 His:DevUidCod 설정으로 뺐다. [권한 게이트] E_ShtMst.ShtUsrDesYon — 'Y' 또는 미설정이면 허용, 그 외만 거부(레거시 frmSheetDesigner.vb:71-84 규칙, 거부 문구도 그대로). 레거시는 게이트가 열기 진입점마다 흩어져 있어 이력 노드 클릭·MessageQueue 두 경로로 우회가 가능했는데, SheetMe 는 모든 열기가 OpenDbSheet 로 수렴하므로 거기 한 곳이면 구조적으로 우회가 불가능하다. 추가로 이력 열람과 DB 저장에도 넣었다 — 후자는 레거시에 아예 없던 구멍이다(열기 이후 플래그가 꺼질 수 있고, 파일에서 연 문서를 DB 에 저장하는 경로는 열기 게이트를 거치지 않는다). 목록에서 거르지 않고 잠금 아이콘으로 표시한다(안 보이면 사용자가 진단할 수 없다). --db-gate 진단 신설 — 실측 결과 차단되는 디자인 보유 서식 0건(N 은 1건뿐이고 디자인 없음). [서버 시각] ServerClock 신설(SELECT SYSTIMESTAMP 왕복 1회로 8/12/17자리 확보, 포맷은 invariant 로 C# 에서 — NLS 의존 제거). 감사 정확성뿐 아니라 ResolveActiveSdgKey 가 ORDER BY SdgUpdDtm 으로 활성 버전을 고르기 때문에, 시계가 뒤로 밀린 단말이 저장하면 활성 버전 판정이 뒤집힐 수 있었다. Reorder 는 루프 밖 1회로 바꿔 한 번의 순서 변경이 행마다 다른 시각을 남기지 않게 했다. 실패 시 폴백하지 않는다. RecordWordStore.Remove 에 auditUid 인자 추가 — 남길 컬럼은 없지만(런타임 조회 쿼리에 ShtStt 필터가 없어 논리 삭제로 바꾸면 지운 문구가 계속 노출된다) "모든 쓰기 API 는 uid 를 명시한다"는 규약을 컴파일 타임에 강제한다. 상용구 대화상자는 uid 를 생성자로 받고, 미인증이면 읽기 전용으로 연다. 검증: 사전 실측으로 M_UidMst/M_DepMst/M_HspMst 접근 확인(2단계 최대 리스크 해소, 유효 사용자 539명). 테스트 70/70, edit-smoke 실패 0(기동 인자 파싱 8건 추가), 왕복 1,271건 diff 0/예외 0, db-save-smoke 제자리 갱신 통과, db-word-smoke 통과. DB 직접 확인: P163 저장분 SdgUidCod='SMOKE', SdgUpdDtm 이 서버 시각과 일치 (변경 전 저장분 S999 는 'Msystech' 로 대비됨). 남은 항목: 단일 인스턴스 + Named Pipe IPC 는 미구현. 기동 인자·게이트가 먼저 필요했고, 바로가기 운영에서는 중복 실행 억제가 덜 급하다. 다중 사용자 병행 운영 전에 처리한다. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
5c97bf1440
commit
0f77cb717a
@@ -5,7 +5,21 @@ using SheetMe.Core.Serialization;
|
||||
namespace SheetMe.Data.Stores;
|
||||
|
||||
/// <summary>서식 목록 1건(열기 대화상자)</summary>
|
||||
public sealed record SheetSummary(string ShtCod, string Name, bool HasDesign);
|
||||
/// <param name="DesignAllowed">서식생성기 사용 허용(E_ShtMst.ShtUsrDesYon) — 목록 잠금 표시용</param>
|
||||
public sealed record SheetSummary(string ShtCod, string Name, bool HasDesign, bool DesignAllowed);
|
||||
|
||||
/// <summary>서식생성기 사용 권한 판정 결과</summary>
|
||||
public enum SheetDesignPermission
|
||||
{
|
||||
/// <summary>E_ShtMst 에 서식 코드가 없음</summary>
|
||||
NotRegistered,
|
||||
|
||||
/// <summary>허용 — ShtUsrDesYon 이 'Y' 또는 미설정</summary>
|
||||
Allowed,
|
||||
|
||||
/// <summary>거부 — ShtUsrDesYon 이 'N' 등</summary>
|
||||
Denied,
|
||||
}
|
||||
|
||||
/// <summary>디자인 버전 1건</summary>
|
||||
public sealed record DesignVersionInfo(decimal SdgKey, string UpdDtm, string UpdUid, bool Deleted);
|
||||
@@ -54,7 +68,8 @@ public sealed class OracleLegacyFormStore
|
||||
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" +
|
||||
" (SELECT COUNT(*) FROM E_SdgMst d WHERE d.SdgShtCod = s.ShtCod AND NVL(d.SdgDelYon,' ') <> 'Y') AS DesignCount," +
|
||||
" NVL(TRIM(s.ShtUsrDesYon),' ') AS UsrDesYon" +
|
||||
$" FROM E_ShtMst s {where} ORDER BY s.ShtCod" +
|
||||
$") WHERE ROWNUM <= {max}";
|
||||
|
||||
@@ -65,11 +80,36 @@ public sealed class OracleLegacyFormStore
|
||||
result.Add(new SheetSummary(
|
||||
reader.GetString(0),
|
||||
reader.IsDBNull(1) ? string.Empty : reader.GetString(1),
|
||||
reader.GetDecimal(2) > 0));
|
||||
reader.GetDecimal(2) > 0,
|
||||
IsDesignAllowed(reader.GetString(3))));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 서식생성기 사용 허용 여부(E_ShtMst.ShtUsrDesYon) — 레거시 frmSheetDesigner.vb:71-84 규칙.
|
||||
/// 'Y' 또는 빈 문자열/NULL(미설정 = 레거시 데이터 호환)이면 허용, 그 외('N' 등)만 거부한다.
|
||||
/// </summary>
|
||||
public SheetDesignPermission GetDesignPermission(string shtCod)
|
||||
{
|
||||
using var connection = Open();
|
||||
var value = Scalar(connection, null,
|
||||
"SELECT NVL(TRIM(ShtUsrDesYon),' ') FROM E_ShtMst WHERE ShtCod = :s",
|
||||
new OracleParameter("s", shtCod)) as string;
|
||||
if (value is null)
|
||||
{
|
||||
return SheetDesignPermission.NotRegistered;
|
||||
}
|
||||
return IsDesignAllowed(value) ? SheetDesignPermission.Allowed : SheetDesignPermission.Denied;
|
||||
}
|
||||
|
||||
/// <summary>ShtUsrDesYon 판정 — CHAR 패딩을 흡수하려 TRIM 후 비교(레거시보다 관대한 방향이라 오탐만 줄인다)</summary>
|
||||
private static bool IsDesignAllowed(string flag)
|
||||
{
|
||||
var trimmed = flag.Trim();
|
||||
return trimmed is "Y" or "";
|
||||
}
|
||||
|
||||
/// <summary>활성(비삭제 최신) 디자인 로드 — 없으면 null. FormId/Title/SourceSdgKey 채움</summary>
|
||||
public FormDocument? LoadActiveDesign(string shtCod)
|
||||
{
|
||||
@@ -214,12 +254,15 @@ public sealed class OracleLegacyFormStore
|
||||
}
|
||||
|
||||
var mergedXml = serializer.Write(document);
|
||||
var now = DateTime.Now.ToString("yyyyMMddHHmm"); // 레거시 SystemDateTime 포맷(12자리)
|
||||
|
||||
using var connection = Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
// 레거시 SystemDateTime 과 동일하게 DB 서버 시각을 쓴다 — 단말 시계가 밀리면
|
||||
// ResolveActiveSdgKey 의 ORDER BY SdgUpdDtm 이 활성 버전을 잘못 고를 수 있다.
|
||||
var now = ServerClock.Read(connection, transaction).Minute12;
|
||||
|
||||
var shtCneYon = Scalar(connection, transaction,
|
||||
"SELECT NVL(ShtCneYon,' ') FROM E_ShtMst WHERE ShtCod = :s",
|
||||
new OracleParameter("s", document.FormId)) as string;
|
||||
@@ -302,17 +345,19 @@ public sealed class OracleLegacyFormStore
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
var stamp = ServerClock.Read(connection, transaction);
|
||||
// ShtUsrDesYon='Y' 하드코딩 — 여기서 만든 서식은 서식생성기가 만든 것이므로 열기 게이트를 자동 통과한다
|
||||
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("adp", stamp.Date8),
|
||||
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")));
|
||||
new OracleParameter("dtm", stamp.Minute12));
|
||||
transaction.Commit();
|
||||
}
|
||||
catch
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Oracle.ManagedDataAccess.Client;
|
||||
using Oracle.ManagedDataAccess.Client;
|
||||
|
||||
namespace SheetMe.Data.Stores;
|
||||
|
||||
@@ -54,6 +54,8 @@ public sealed class RecordWordStore
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
// 서버 시각 1회 — RETURNING 경로와 폴백 경로가 같은 값을 쓰게 한다
|
||||
var stamp = ServerClock.Read(connection, transaction).Milli17;
|
||||
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))!);
|
||||
@@ -74,7 +76,7 @@ public sealed class RecordWordStore
|
||||
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()));
|
||||
command.Parameters.Add(new OracleParameter("d", stamp));
|
||||
var outKey = new OracleParameter("outKey", OracleDbType.Decimal, System.Data.ParameterDirection.Output);
|
||||
command.Parameters.Add(outKey);
|
||||
command.ExecuteNonQuery();
|
||||
@@ -94,7 +96,7 @@ public sealed class RecordWordStore
|
||||
new OracleParameter("v", value),
|
||||
new OracleParameter("q", seq),
|
||||
new OracleParameter("u", uid),
|
||||
new OracleParameter("d", Now17()));
|
||||
new OracleParameter("d", stamp));
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
@@ -111,20 +113,28 @@ public sealed class RecordWordStore
|
||||
public void Update(decimal key, string value, string uid)
|
||||
{
|
||||
using var connection = Open();
|
||||
var stamp = ServerClock.Read(connection, null).Milli17;
|
||||
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("d", stamp));
|
||||
command.Parameters.Add(new OracleParameter("k", key));
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
/// <summary>상용구 삭제(하위 단계 포함)</summary>
|
||||
public void Remove(decimal key)
|
||||
/// <summary>
|
||||
/// 상용구 삭제(하위 단계 포함) — 물리 DELETE.
|
||||
/// auditUid 를 DB 에 남길 곳은 없다: 레거시 런타임 조회 쿼리(dtRecWordMst.vb)에 ShtStt 필터가 없어
|
||||
/// 논리 삭제로 바꾸면 지운 문구가 EMR 작성 화면에 계속 노출된다. 그럼에도 인자로 받는 이유는
|
||||
/// "모든 쓰기 API 는 uid 를 명시한다"는 규약을 컴파일 타임에 강제해, 나중에 uid 없는 경로가
|
||||
/// 새로 생기지 않게 하기 위해서다.
|
||||
/// </summary>
|
||||
public void Remove(decimal key, string auditUid)
|
||||
{
|
||||
_ = auditUid;
|
||||
using var connection = Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
@@ -149,13 +159,15 @@ public sealed class RecordWordStore
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
// 루프 밖에서 1회 — 한 번의 순서 변경이 행마다 다른 시각을 남기지 않게 한다
|
||||
var stamp = ServerClock.Read(connection, transaction).Milli17;
|
||||
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("d", stamp),
|
||||
new OracleParameter("k", orderedKeys[i]));
|
||||
}
|
||||
transaction.Commit();
|
||||
@@ -174,9 +186,6 @@ public sealed class RecordWordStore
|
||||
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();
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using System.Globalization;
|
||||
using Oracle.ManagedDataAccess.Client;
|
||||
|
||||
namespace SheetMe.Data.Stores;
|
||||
|
||||
/// <summary>
|
||||
/// 감사 컬럼 시각의 유일한 출처 = DB 서버 시각(레거시 <c>clsCommonLib.SystemDateTime</c> 동형 —
|
||||
/// 레거시도 <c>SELECT Sysdate FROM DUAL</c> 로 서버 시각을 받아 클라이언트에서 포맷한다).
|
||||
///
|
||||
/// 단순한 감사 정확성 문제가 아니다. <c>ResolveActiveSdgKey</c> 가
|
||||
/// <c>ORDER BY SdgUpdDtm DESC, SdgKey DESC</c> 로 활성 버전을 고르므로,
|
||||
/// PC 시계가 뒤로 밀린 단말에서 저장하면 활성 버전 판정이 뒤집힐 수 있다.
|
||||
/// </summary>
|
||||
internal static class ServerClock
|
||||
{
|
||||
#region Types
|
||||
/// <summary>서버 시각 1회 조회 결과 — 레거시가 쓰는 3가지 포맷을 함께 제공</summary>
|
||||
internal readonly record struct Stamp(DateTime Value)
|
||||
{
|
||||
/// <summary>yyyyMMdd (8자리) — ShtAdpDte 류</summary>
|
||||
public string Date8 => Value.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
|
||||
|
||||
/// <summary>yyyyMMddHHmm (12자리) — SdgUpdDtm/ShtUpdDtm</summary>
|
||||
public string Minute12 => Value.ToString("yyyyMMddHHmm", CultureInfo.InvariantCulture);
|
||||
|
||||
/// <summary>yyyyMMddHHmmssfff (17자리) — 상용구 ShtUpdDtf/ShtFstDtf</summary>
|
||||
public string Milli17 => Value.ToString("yyyyMMddHHmmssfff", CultureInfo.InvariantCulture);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
/// <summary>
|
||||
/// 서버 시각 조회 — 왕복 1회로 3포맷을 확보한다.
|
||||
/// 포맷 마스크를 SQL 에 두지 않고 값만 받아 invariant 로 포맷해 NLS 설정 의존을 없앤다.
|
||||
/// 실패 시 폴백하지 않는다 — 커넥션이 죽은 것이고 뒤따르는 DML 도 어차피 실패한다.
|
||||
/// </summary>
|
||||
public static Stamp Read(OracleConnection connection, OracleTransaction? transaction)
|
||||
{
|
||||
using var command = connection.CreateCommand();
|
||||
if (transaction is not null)
|
||||
{
|
||||
command.Transaction = transaction;
|
||||
}
|
||||
command.CommandText = "SELECT SYSTIMESTAMP FROM DUAL";
|
||||
using var reader = command.ExecuteReader();
|
||||
reader.Read();
|
||||
return new Stamp(reader.GetDateTime(0));
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using Oracle.ManagedDataAccess.Client;
|
||||
|
||||
namespace SheetMe.Data.Stores;
|
||||
|
||||
/// <summary>
|
||||
/// HIS 로그인 사용자 — 감사 컬럼(SdgUidCod/ShtUpdUid 등)에 기록되는 UidCod 의 유일한 출처.
|
||||
/// 부서·병원은 표시 전용이며 없어도 인증은 성립한다(레거시 bzLogIn 과 동일 의미론).
|
||||
/// </summary>
|
||||
public sealed record HisUser(
|
||||
string UidCod, string UidNam,
|
||||
string DepCod, string DepNam,
|
||||
string HspCod, string HspNam)
|
||||
{
|
||||
/// <summary>미인증 세션(인자 없이 단독 실행) — DB 쓰기가 차단된다</summary>
|
||||
public static readonly HisUser Unknown = new(string.Empty, string.Empty, string.Empty, string.Empty, string.Empty, string.Empty);
|
||||
|
||||
/// <summary>HIS 사용자로 확인되었는지</summary>
|
||||
public bool IsAuthenticated => UidCod.Length > 0;
|
||||
|
||||
/// <summary>표시용 — "홍길동(MSYS)" 형태, 미인증이면 안내 문구</summary>
|
||||
public string Display => IsAuthenticated
|
||||
? (UidNam.Length > 0 ? $"{UidNam}({UidCod})" : UidCod)
|
||||
: "사용자 미확인";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 사용자 컨텍스트 조회 — 레거시 bzLogIn.LogIn(uid, autoLogin) 의 신규 대응물.
|
||||
/// 레거시도 자동 로그인에서는 비밀번호를 확인하지 않고 M_UidMst 행 존재 + 유효기간만 판정한다
|
||||
/// (daLogIn.vb: SELECT * FROM M_UidMst WHERE UidCod=:u AND :adp BETWEEN UidStrDte AND UidEndDte).
|
||||
/// </summary>
|
||||
public sealed class UserContextStore
|
||||
{
|
||||
#region Member Fields
|
||||
private readonly string connectionString;
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
public UserContextStore(string connectionString)
|
||||
{
|
||||
this.connectionString = connectionString;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
/// <summary>
|
||||
/// UidCod 검증 + 표시 정보 조회 — 미존재이거나 유효기간 밖이면 null.
|
||||
/// 적용일자는 서버 시각을 쓴다(단말 시계가 밀리면 유효 계정이 거부될 수 있다).
|
||||
/// 부서·병원은 LEFT JOIN 이라 마스터에 행이 없어도 인증 자체는 성립한다.
|
||||
/// </summary>
|
||||
public HisUser? Resolve(string uidCod)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(uidCod))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using var connection = new OracleConnection(connectionString);
|
||||
connection.Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.BindByName = true;
|
||||
command.CommandText =
|
||||
"SELECT u.UidCod, NVL(u.UidNam,' '), " +
|
||||
" NVL(u.UidDepCod,' '), NVL(d.DepKorNam,' '), " +
|
||||
" NVL(u.UidHspCod,' '), NVL(h.HspInsNam,' ') " +
|
||||
"FROM M_UidMst u " +
|
||||
"LEFT JOIN M_DepMst d ON d.DepCod = u.UidDepCod " +
|
||||
" AND TO_CHAR(SYSDATE,'YYYYMMDD') BETWEEN d.DepStrDte AND d.DepEndDte " +
|
||||
"LEFT JOIN M_HspMst h ON h.HspCod = u.UidHspCod " +
|
||||
" AND TO_CHAR(SYSDATE,'YYYYMMDD') BETWEEN h.HspStrDte AND NVL(h.HspEndDte,'99991231') " +
|
||||
"WHERE u.UidCod = :u " +
|
||||
"AND TO_CHAR(SYSDATE,'YYYYMMDD') BETWEEN u.UidStrDte AND u.UidEndDte";
|
||||
command.Parameters.Add(new OracleParameter("u", uidCod.Trim()));
|
||||
|
||||
using var reader = command.ExecuteReader();
|
||||
if (!reader.Read())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new HisUser(
|
||||
reader.GetString(0).Trim(), reader.GetString(1).Trim(),
|
||||
reader.GetString(2).Trim(), reader.GetString(3).Trim(),
|
||||
reader.GetString(4).Trim(), reader.GetString(5).Trim());
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user