@ -38,6 +38,12 @@ namespace marking_gui
// 계측기 연동 서비스 (DMM 전압 + Power Supply 전류)
// 계측기 연동 서비스 (DMM 전압 + Power Supply 전류)
private EquipmentMeasurementService _ equipment ;
private EquipmentMeasurementService _ equipment ;
// 검사 취소 토큰 및 진행 상태 관리
private CancellationTokenSource ? _ inspectionCts ;
private bool _ isInspectionRunning = false ;
private bool _ isRestoringDevices = false ;
private bool _ isInitialLoading = true ;
// 검사 시작 / 종료 시간 추적
// 검사 시작 / 종료 시간 추적
private DateTime _ testStartTime = DateTime . MinValue ;
private DateTime _ testStartTime = DateTime . MinValue ;
private DateTime _ testEndTime = DateTime . MinValue ;
private DateTime _ testEndTime = DateTime . MinValue ;
@ -82,6 +88,9 @@ namespace marking_gui
_d bWatcherCts = new CancellationTokenSource ( ) ;
_d bWatcherCts = new CancellationTokenSource ( ) ;
StartDbReconnectWatcher ( _d bWatcherCts . Token ) ;
StartDbReconnectWatcher ( _d bWatcherCts . Token ) ;
}
}
// 초기 로딩 시점 스타트 버튼 비활성화 텍스트 표기 보장
SetStartButtonConnecting ( "⏳ DB 연결 및 순번 조회 중..." ) ;
}
}
private void ApplyTestModeSettings ( )
private void ApplyTestModeSettings ( )
@ -351,8 +360,26 @@ namespace marking_gui
txtResistanceRangeGuide . Text = string . Format ( "기준범위: {0:F3}kΩ ~ {1:F3}kΩ" , _l imits . MinResistance , _l imits . MaxResistance ) ;
txtResistanceRangeGuide . Text = string . Format ( "기준범위: {0:F3}kΩ ~ {1:F3}kΩ" , _l imits . MinResistance , _l imits . MaxResistance ) ;
}
}
private bool CheckUserLoggedIn ( )
{
if ( _l oginInfo = = null | | string . IsNullOrWhiteSpace ( _l oginInfo . Operator ) | | _l oginInfo . Operator = = "---" )
{
MessageBox . Show ( "작업자 로그인 후 이용 가능합니다." , "로그인 필요" , MessageBoxButton . OK , MessageBoxImage . Warning ) ;
return false ;
}
return true ;
}
private void btnOpenSettings_Click ( object sender , RoutedEventArgs e )
private void btnOpenSettings_Click ( object sender , RoutedEventArgs e )
{
{
if ( ! CheckUserLoggedIn ( ) ) return ;
if ( _ isInspectionRunning )
{
MessageBox . Show ( "현재 검사가 진행 중입니다.\n검사가 완료되거나 중지된 후 설정을 변경하십시오." , "설정 변경 불가" , MessageBoxButton . OK , MessageBoxImage . Warning ) ;
return ;
}
SettingsWindow settings = new SettingsWindow ( _l imits ) ;
SettingsWindow settings = new SettingsWindow ( _l imits ) ;
settings . Owner = this ;
settings . Owner = this ;
@ -368,6 +395,14 @@ namespace marking_gui
private void btnOpenPortSettings_Click ( object sender , RoutedEventArgs e )
private void btnOpenPortSettings_Click ( object sender , RoutedEventArgs e )
{
{
if ( ! CheckUserLoggedIn ( ) ) return ;
if ( _ isInspectionRunning )
{
MessageBox . Show ( "현재 검사가 진행 중입니다.\n검사가 완료되거나 중지된 후 포트 설정을 변경하십시오." , "포트 설정 변경 불가" , MessageBoxButton . OK , MessageBoxImage . Warning ) ;
return ;
}
PortSettingsWindow portSettings = new PortSettingsWindow ( ) ;
PortSettingsWindow portSettings = new PortSettingsWindow ( ) ;
portSettings . Owner = this ;
portSettings . Owner = this ;
@ -381,22 +416,198 @@ namespace marking_gui
}
}
// ── 버튼 상태 헬퍼 ────────────────────────────────────────────────
// ── 버튼 상태 헬퍼 ────────────────────────────────────────────────
/// <summary>검사 진행 중 → 버튼 비활성화 + 텍스트 변경 </summary>
/// <summary>검사 진행 중 → 버튼을 [중지] 버튼으로 전환 </summary>
private void SetStartButtonBusy ( )
private void SetStartButtonBusy ( )
{
{
btnStart . IsEnabled = false ;
_ isInspectionRunning = true ;
btnStart . Content = "⏳ 검사 진행 중..." ;
if ( btnStart ! = null )
{
btnStart . IsEnabled = true ;
btnStart . Content = "⏹ 검사 중지 (STOP)" ;
try
{
var stopStyle = FindResource ( "StopButtonStyle" ) as Style ;
if ( stopStyle ! = null ) btnStart . Style = stopStyle ;
}
catch { }
}
if ( btnBypassStart ! = null ) btnBypassStart . IsEnabled = false ;
if ( btnBypassStart ! = null ) btnBypassStart . IsEnabled = false ;
}
}
/// <summary>검사 완료/취소 → 버튼 활성화 + 텍스트 원복</summary>
/// <summary>장비 복구 진행 중 → 버튼 비활성화 + 시작 차단</summary>
private void SetStartButtonRestoring ( )
{
_ isInspectionRunning = true ;
if ( btnStart ! = null )
{
btnStart . IsEnabled = false ;
btnStart . Content = "⏳ 장비 복구 중..." ;
try
{
var stopStyle = FindResource ( "StopButtonStyle" ) as Style ;
if ( stopStyle ! = null ) btnStart . Style = stopStyle ;
}
catch { }
}
if ( btnBypassStart ! = null ) btnBypassStart . IsEnabled = false ;
}
/// <summary>DB 또는 장치 연결 시도/실패 중 → 스타트 버튼 비활성화 + 지정 텍스트 표기</summary>
private void SetStartButtonConnecting ( string statusText = "⚠️ DB 연결 필요" )
{
if ( _ isRestoringDevices ) return ;
if ( btnStart ! = null )
{
btnStart . IsEnabled = false ;
btnStart . Content = statusText ;
try
{
var stopStyle = FindResource ( "StopButtonStyle" ) as Style ;
if ( stopStyle ! = null ) btnStart . Style = stopStyle ;
}
catch { }
}
if ( btnBypassStart ! = null ) btnBypassStart . IsEnabled = false ;
}
/// <summary>검사 완료/취소/대기 → 버튼을 [시작] 버튼으로 복구 (DB 미연결 시 ⚠️ DB 연결 필요 표기 유지)</summary>
private void SetStartButtonReady ( )
private void SetStartButtonReady ( )
{
{
btnStart . IsEnabled = true ;
// 장비 복구 수순이 진행 중이거나 초기 로딩 중인 동안에는 Ready 상태로 전환 차단
btnStart . Content = "▶ 검사 시작 (START)" ;
if ( _ isRestoringDevices | | _ isInitialLoading ) return ;
// 만약 오프라인 모드가 아닌데 DB가 연결되어 있지 않은 상태라면 검사 시작 버튼을 '⚠️ DB 연결 필요' 상태로 계속 유지
if ( _d atabase ! = null & & ! _d atabase . OfflineMode & & _d bWasOffline )
{
_ isInspectionRunning = false ;
if ( btnStart ! = null )
{
btnStart . IsEnabled = false ;
btnStart . Content = "⚠️ DB 연결 필요" ;
try
{
var stopStyle = FindResource ( "StopButtonStyle" ) as Style ;
if ( stopStyle ! = null ) btnStart . Style = stopStyle ;
}
catch { }
}
if ( btnBypassStart ! = null ) btnBypassStart . IsEnabled = false ;
return ;
}
_ isInspectionRunning = false ;
if ( btnStart ! = null )
{
btnStart . IsEnabled = true ;
btnStart . Content = "▶ 검사 시작 (START)" ;
try
{
var startStyle = FindResource ( "StartButtonStyle" ) as Style ;
if ( startStyle ! = null ) btnStart . Style = startStyle ;
}
catch { }
}
if ( btnBypassStart ! = null ) btnBypassStart . IsEnabled = true ;
if ( btnBypassStart ! = null ) btnBypassStart . IsEnabled = true ;
}
}
/// <summary>사용자가 [STOP] 버튼을 클릭했을 때 검사 중단 및 장비 정상 상태 리셋</summary>
private async System . Threading . Tasks . Task CancelInspectionSequenceAsync ( )
{
if ( _ isRestoringDevices ) return ;
_ isRestoringDevices = true ; // 복구 잠금 시작
LoggerService . Warn ( "[검사 중지] 사용자가 [STOP] 검사 중지 버튼을 클릭했습니다." ) ;
// 장비 복구 작업 완료 시까지 버튼 클릭 차단 (비활성화)
SetStartButtonRestoring ( ) ;
if ( txtProgressStatus ! = null )
{
txtProgressStatus . Text = "검사 중지 중 (장비 복구 진행 중...)" ;
txtProgressStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 3 9 , 6 8 , 6 8 ) ) ;
}
if ( txtSystemStatus ! = null )
{
txtSystemStatus . Text = "상태: 검사 중지 요청됨 - 장비 초기화 중..." ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 4 5 , 1 5 8 , 1 1 ) ) ;
}
try
{
_ inspectionCts ? . Cancel ( ) ;
}
catch ( Exception ex )
{
LoggerService . Warn ( $"CancellationTokenSource 취소 중 예외: {ex.Message}" ) ;
}
await ResetDevicesToReadyStateAsync ( ) ;
// 장비 복구 완결 후 비로소 잠금 해제
_ isRestoringDevices = false ;
SetStartButtonReady ( ) ;
if ( txtSerial ! = null ) txtSerial . IsEnabled = true ;
if ( txtProgressStatus ! = null )
{
txtProgressStatus . Text = "대기 중" ;
txtProgressStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 1 0 7 , 1 1 4 , 1 2 8 ) ) ; // Gray
}
if ( txtSystemStatus ! = null )
{
txtSystemStatus . Text = "상태: 대기 중" ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 5 9 , 1 3 0 , 2 4 6 ) ) ; // Blue
}
}
/// <summary>DMM 계측기 및 레이저 마킹 장비를 재시작 가능한 정상 초기 상태로 복구</summary>
private async System . Threading . Tasks . Task ResetDevicesToReadyStateAsync ( )
{
try
{
// 1. PSU 파워서플라이 출력 강제 OFF (측정 중단 시에도 출력이 켜진 상태로 남지 않도록 보장)
if ( _ equipment ! = null )
{
await System . Threading . Tasks . Task . Run ( async ( ) = >
{
try
{
await _ equipment . ResetAsync ( ) ;
LoggerService . Info ( "[장비 리셋] DMM / PSU 출력 OFF 정상 완료" ) ;
}
catch ( Exception ex )
{
LoggerService . Warn ( $"[장비 리셋] DMM/PSU 복구 예외: {ex.Message}" ) ;
}
} ) ;
}
// 2. 레이저 마킹 장비 중단 및 핸들 락 해제
if ( _ markingService ! = null )
{
await System . Threading . Tasks . Task . Run ( ( ) = >
{
try
{
_ markingService . StopMarking ( ) ;
LoggerService . Info ( "[장비 리셋] 레이저 마킹 장비 StopMarking 호출 완료" ) ;
}
catch ( Exception ex )
{
LoggerService . Warn ( $"[장비 리셋] 마킹 장비 중단 예외: {ex.Message}" ) ;
}
} ) ;
}
}
catch ( Exception ex )
{
LoggerService . Error ( "[장비 복구 오류] 장비 복구 처리 중 예외 발생" , ex ) ;
}
}
// UI 초기화 상태
// UI 초기화 상태
private void ResetUI ( )
private void ResetUI ( )
{
{
@ -493,6 +704,15 @@ namespace marking_gui
// 스타트 버튼 클릭
// 스타트 버튼 클릭
private async void btnStart_Click ( object sender , RoutedEventArgs e )
private async void btnStart_Click ( object sender , RoutedEventArgs e )
{
{
// 장비 복구 중인 경우 버튼 클릭 완전 금지
if ( _ isRestoringDevices ) return ;
if ( _ isInspectionRunning )
{
await CancelInspectionSequenceAsync ( ) ;
return ;
}
await RunInspectionSequenceAsync ( bypassPrevSteps : false ) ;
await RunInspectionSequenceAsync ( bypassPrevSteps : false ) ;
}
}
@ -505,6 +725,8 @@ namespace marking_gui
// 스캔 테스트 버튼 클릭 (바코드 / QR 스캐너 연동 독립 테스트)
// 스캔 테스트 버튼 클릭 (바코드 / QR 스캐너 연동 독립 테스트)
private void btnScanTest_Click ( object sender , RoutedEventArgs e )
private void btnScanTest_Click ( object sender , RoutedEventArgs e )
{
{
if ( ! CheckUserLoggedIn ( ) ) return ;
string testSampleQr = "TEST_SN_12345;DRAWING_SAMPLE;HKMC_SAMPLE;2607299999;" ;
string testSampleQr = "TEST_SN_12345;DRAWING_SAMPLE;HKMC_SAMPLE;2607299999;" ;
var qrTestWin = new QrVerificationWindow ( testSampleQr , _ barcodeScanner )
var qrTestWin = new QrVerificationWindow ( testSampleQr , _ barcodeScanner )
{
{
@ -526,13 +748,18 @@ namespace marking_gui
private async System . Threading . Tasks . Task RunInspectionSequenceAsync ( bool bypassPrevSteps )
private async System . Threading . Tasks . Task RunInspectionSequenceAsync ( bool bypassPrevSteps )
{
{
// 진입하자마자 즉각 버튼을 비활성화하여 중복 클릭 원천 차단 (비동기 대기 이전에 호출 필수)
_ inspectionCts ? . Dispose ( ) ;
_ inspectionCts = new CancellationTokenSource ( ) ;
var token = _ inspectionCts . Token ; // 이 시퀀스 전체에서 공유할 취소 토큰
// 진입 즉시 버튼을 [⏹ 검사 중지 (STOP)] 상태로 변경 (사용자 요청 반영)
SetStartButtonBusy ( ) ;
SetStartButtonBusy ( ) ;
// 0. 각인 일련번호 사전 초과 검증 (9999 초과 시 테스트 시작 차단)
// 0. 각인 일련번호 사전 초과 검증 (9999 초과 시 테스트 시작 차단)
int checkSeq ;
int checkSeq ;
try
try
{
{
token . ThrowIfCancellationRequested ( ) ;
checkSeq = await GetNextMarkingSequenceAsync ( _l oginInfo . LotNo ) ;
checkSeq = await GetNextMarkingSequenceAsync ( _l oginInfo . LotNo ) ;
}
}
catch ( Exception seqEx )
catch ( Exception seqEx )
@ -575,7 +802,7 @@ namespace marking_gui
// 검사 시작 누르자마자 이전의 측정 결과 및 상태 화면을 즉시 클리어
// 검사 시작 누르자마자 이전의 측정 결과 및 상태 화면을 즉시 클리어
ResetUI ( ) ;
ResetUI ( ) ;
// ResetUI 내부에서 버튼이 다시 활성화(Ready)되므로 Busy 상태로 잠금 유지
// ResetUI 실행 후에도 버튼 상태를 [⏹ 검사 중지 (STOP)] 유지
SetStartButtonBusy ( ) ;
SetStartButtonBusy ( ) ;
// 검사 시작 즉시 IC_SN 입력란을 초기화 (이전 S/N이 남지 않도록)
// 검사 시작 즉시 IC_SN 입력란을 초기화 (이전 S/N이 남지 않도록)
@ -646,6 +873,7 @@ namespace marking_gui
bool isDbConnected = true ;
bool isDbConnected = true ;
if ( ! bypassPrevSteps )
if ( ! bypassPrevSteps )
{
{
token . ThrowIfCancellationRequested ( ) ;
txtSystemStatus . Text = "상태: DB 연결 확인 중..." ;
txtSystemStatus . Text = "상태: DB 연결 확인 중..." ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 5 9 , 1 3 0 , 2 4 6 ) ) ; // Blue
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 5 9 , 1 3 0 , 2 4 6 ) ) ; // Blue
if ( txtProgressStatus ! = null )
if ( txtProgressStatus ! = null )
@ -654,7 +882,7 @@ namespace marking_gui
txtProgressStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 5 9 , 1 3 0 , 2 4 6 ) ) ; // Blue
txtProgressStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 5 9 , 1 3 0 , 2 4 6 ) ) ; // Blue
}
}
isDbConnected = await System . Threading . Tasks . Task . Run ( ( ) = > _d atabase . CheckConnection ( ) ) ;
isDbConnected = await System . Threading . Tasks . Task . Run ( ( ) = > _d atabase . CheckConnection ( ) , token ) ;
}
}
if ( ! bypassPrevSteps & & ! isDbConnected )
if ( ! bypassPrevSteps & & ! isDbConnected )
@ -677,6 +905,7 @@ namespace marking_gui
// ── STEP 3. UI 상태 갱신 및 이전 공정 조회 준비 ─────────────────────
// ── STEP 3. UI 상태 갱신 및 이전 공정 조회 준비 ─────────────────────
// 새로운 검사 진행을 위해 UI 상태를 초기화
// 새로운 검사 진행을 위해 UI 상태를 초기화
ResetUI ( ) ;
ResetUI ( ) ;
SetStartButtonBusy ( ) ;
// UI 상태 복원 및 진행 개시 알림
// UI 상태 복원 및 진행 개시 알림
txtSerial . Text = serial ;
txtSerial . Text = serial ;
@ -701,7 +930,8 @@ namespace marking_gui
{
{
try
try
{
{
product = await System . Threading . Tasks . Task . Run ( ( ) = > _d atabase . GetProductBySerial ( serial ) ) ;
token . ThrowIfCancellationRequested ( ) ;
product = await System . Threading . Tasks . Task . Run ( ( ) = > _d atabase . GetProductBySerial ( serial ) , token ) ;
_ currentProduct = product ; // EOL PCB 바코드 포함 저장
_ currentProduct = product ; // EOL PCB 바코드 포함 저장
}
}
catch ( Exception ex )
catch ( Exception ex )
@ -883,7 +1113,8 @@ namespace marking_gui
if ( shouldProceed )
if ( shouldProceed )
{
{
// ── STEP 4. IC S/N DB 중복 여부 검사 (재작업 확인 팝업) ────────────────
// ── STEP 4. IC S/N DB 중복 여부 검사 (재작업 확인 팝업) ────────────────
bool isDuplicate = await System . Threading . Tasks . Task . Run ( ( ) = > _d atabase . IsProductExists ( serial ) ) ;
token . ThrowIfCancellationRequested ( ) ;
bool isDuplicate = await System . Threading . Tasks . Task . Run ( ( ) = > _d atabase . IsProductExists ( serial ) , token ) ;
if ( isDuplicate )
if ( isDuplicate )
{
{
LoggerService . Warn ( $"[재작업 감지] IC S/N ({serial})가 이미 DB에 존재합니다. 작업자 확인 팝업 표시" ) ;
LoggerService . Warn ( $"[재작업 감지] IC S/N ({serial})가 이미 DB에 존재합니다. 작업자 확인 팝업 표시" ) ;
@ -955,11 +1186,18 @@ namespace marking_gui
try
try
{
{
var measurement = await ReadMeasurementFromDeviceAsync ( ) ;
token . ThrowIfCancellationRequested ( ) ;
var measurement = await ReadMeasurementFromDeviceAsync ( token ) ;
token . ThrowIfCancellationRequested ( ) ; // 계측 완료 후에도 즉시 취소 확인
finalVoltage = measurement . Voltage ;
finalVoltage = measurement . Voltage ;
finalCurrent = measurement . Current ;
finalCurrent = measurement . Current ;
finalResistance = measurement . Resistance ;
finalResistance = measurement . Resistance ;
}
}
catch ( OperationCanceledException )
{
LoggerService . Warn ( "[검사 중지] 계측 단계에서 중지 요청이 수신되었습니다." ) ;
return ; // CancelInspectionSequenceAsync가 장비 복구를 담당
}
catch ( Exception measEx )
catch ( Exception measEx )
{
{
LoggerService . Error ( "계측기 데이터 수집 중 예외 발생" , measEx ) ;
LoggerService . Error ( "계측기 데이터 수집 중 예외 발생" , measEx ) ;
@ -1014,26 +1252,27 @@ namespace marking_gui
if ( isFinalPass )
if ( isFinalPass )
{
{
txtSystemStatus . Text = "상태: 검사 완료 (합격 )" ;
txtSystemStatus . Text = "상태: 전기적 계측 합격 (각인/스캔 대기 )" ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 1 6 , 1 8 5 , 1 2 9 ) ) ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 1 6 , 1 8 5 , 1 2 9 ) ) ;
txtFinalResult . Text = "OK" ;
// 최종 OK 표기는 스캔 검증까지 다 성공했을 때 처리되도록 대기 상태 유지
txtFinalResult . Foreground = new SolidColorBrush ( Color . FromRgb ( 1 6 , 1 8 5 , 1 2 9 ) ) ; // Green text for white card
txtFinalResult . Text = "대기 중" ;
txtFinalResult . Foreground = new SolidColorBrush ( Color . FromRgb ( 1 5 6 , 1 6 3 , 1 7 5 ) ) ; // Gray
if ( borderVerdictCard ! = null )
if ( borderVerdictCard ! = null )
{
{
borderVerdictCard . Background = new SolidColorBrush ( Color . FromRgb ( 2 4 0 , 2 5 3 , 2 5 0 ) ) ; // Light Green background
borderVerdictCard . Background = System . Windows . Media . Brushes . White ;
borderVerdictCard . BorderBrush = new SolidColorBrush ( Color . FromRgb ( 1 6 , 1 8 5 , 1 2 9 ) ) ; // Green border
borderVerdictCard . BorderBrush = new SolidColorBrush ( Color . FromRgb ( 2 2 9 , 2 3 1 , 2 3 5 ) ) ;
borderVerdictCard . BorderThickness = new Thickness ( 3 ) ;
borderVerdictCard . BorderThickness = new Thickness ( 1 ) ;
}
}
if ( badgeFinalResult ! = null )
if ( badgeFinalResult ! = null )
{
{
badgeFinalResult . Background = System . Windows . Media . Brushes . White ; // Keep Progress Status banner white
badgeFinalResult . Background = System . Windows . Media . Brushes . White ;
}
}
borderFinalResult . Background = new System . Windows . Media . SolidColorBrush ( System . Windows . Media . Color . FromRgb ( 2 4 3 , 2 4 4 , 2 4 6 ) ) ; // Default Light Gray container
borderFinalResult . Background = new System . Windows . Media . SolidColorBrush ( System . Windows . Media . Color . FromRgb ( 2 4 3 , 2 4 4 , 2 4 6 ) ) ;
if ( txtProgressStatus ! = null )
if ( txtProgressStatus ! = null )
{
{
txtProgressStatus . Text = "제품 합격 - 레이저 각인 중..." ;
txtProgressStatus . Text = "계측 합격 - 레이저 각인 및 QR 스캔 진행 중..." ;
txtProgressStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 4 5 , 1 5 8 , 1 1 ) ) ; // Amber/Orange text
txtProgressStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 4 5 , 1 5 8 , 1 1 ) ) ; // Amber/Orange text
}
}
}
}
@ -1072,23 +1311,35 @@ namespace marking_gui
if ( txtMarkingStatus ! = null ) txtMarkingStatus . Text = "마킹 중..." ;
if ( txtMarkingStatus ! = null ) txtMarkingStatus . Text = "마킹 중..." ;
if ( borderMarkingStatus ! = null ) borderMarkingStatus . Background = new SolidColorBrush ( Color . FromRgb ( 2 4 5 , 1 5 8 , 1 1 ) ) ; // Amber/Orange
if ( borderMarkingStatus ! = null ) borderMarkingStatus . Background = new SolidColorBrush ( Color . FromRgb ( 2 4 5 , 1 5 8 , 1 1 ) ) ; // Amber/Orange
_ = System . Threading . Tasks . Task . Run ( async ( ) = >
// fire-and-forget 대신 await로 대기하여 취소 토큰이 즉시 반영되도록 변경
await System . Threading . Tasks . Task . Run ( async ( ) = >
{
{
try
try
{
{
// 1. 각인 일련번호 조회 및 생성 (YYMMDDLL + 4자리 시퀀스)
// 마킹 진입 전 취소 토큰 최종 확인
int seq ;
token . ThrowIfCancellationRequested ( ) ;
try
// 1. 각인 일련번호 생성 (검사 시작 전 화면에 표시된 순번 그대로 사용)
int seq = 1 ;
if ( txtNextSequence ! = null )
{
{
seq = await GetNextMarkingSequenceAsync ( _l oginInfo . LotNo ) ;
Dispatcher . Invoke ( ( ) = >
{
string rawText = txtNextSequence . Text ;
string digitsOnly = System . Text . RegularExpressions . Regex . Replace ( rawText ? ? "" , @"[^\d]" , "" ) ;
if ( int . TryParse ( digitsOnly , out int parsedSeq ) & & parsedSeq > 0 )
{
seq = parsedSeq ;
}
} ) ;
}
}
catch ( Exception seqEx )
if ( seq > 9 9 9 9 )
{
{
LoggerService . Error ( "[순번 조회 실패] 마킹 직전 DB 순번 조회 실패" , seqEx ) ;
Dispatcher . Invoke ( ( ) = >
Dispatcher . Invoke ( ( ) = >
{
{
string errMsg = $"각인 순번 DB 조회 실패: {seqEx.Message}" ;
string errMsg = "마킹 직전 순번이 9999를 초과했습니다. 다음 Lot No로 변경하십시오. ";
if ( txtMarkingStatus ! = null ) txtMarkingStatus . Text = "순번 조회 실패" ;
if ( txtMarkingStatus ! = null ) txtMarkingStatus . Text = "순번 초과 " ;
if ( borderMarkingStatus ! = null ) borderMarkingStatus . Background = new SolidColorBrush ( Color . FromRgb ( 2 3 9 , 6 8 , 6 8 ) ) ;
if ( borderMarkingStatus ! = null ) borderMarkingStatus . Background = new SolidColorBrush ( Color . FromRgb ( 2 3 9 , 6 8 , 6 8 ) ) ;
if ( txtSystemStatus ! = null )
if ( txtSystemStatus ! = null )
{
{
@ -1096,21 +1347,22 @@ namespace marking_gui
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 3 9 , 6 8 , 6 8 ) ) ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 3 9 , 6 8 , 6 8 ) ) ;
}
}
if ( txtErrorMessage ! = null ) txtErrorMessage . Text = errMsg ;
if ( txtErrorMessage ! = null ) txtErrorMessage . Text = errMsg ;
if ( borderErrorBanner ! = null ) borderErrorBanner . Visibility = System . Windows . Visibility . Visible ;
if ( borderErrorBanner ! = null ) borderErrorBanner . Visibility = Visibility . Visible ;
if ( borderNormalStatus ! = null ) borderNormalStatus . Visibility = System . Windows . Visibility . Collapsed ;
if ( borderNormalStatus ! = null ) borderNormalStatus . Visibility = Visibility . Collapsed ;
if ( txtProgressStatus ! = null )
if ( txtProgressStatus ! = null )
{
{
txtProgressStatus . Text = "마킹 실패 (순번 조회 불가 )" ;
txtProgressStatus . Text = "마킹 실패 (순번 초과 )" ;
txtProgressStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 3 9 , 6 8 , 6 8 ) ) ;
txtProgressStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 3 9 , 6 8 , 6 8 ) ) ;
}
}
MessageBox . Show ( $"각인 순번을 DB에서 가져오지 못했습니다.\n\n{seqEx.Message}\n\nDB 연결 상태를 확인하십시오." , "순번 조회 실패" , MessageBoxButton . OK , MessageBoxImage . Error ) ;
MessageBox . Show ( errMsg , "순번 초과 오류" , MessageBoxButton . OK , MessageBoxImage . Error ) ;
SetStartButtonReady ( ) ;
txtSerial . IsEnabled = true ;
} ) ;
} ) ;
return ;
return ;
}
}
string yymmdd = DateTime . Now . ToString ( "yyMMdd" ) ;
string lotNo = string . IsNullOrWhiteSpace ( _l oginInfo . LotNo ) ? "00" : _l oginInfo . LotNo . Trim ( ) ;
string lotNo = string . IsNullOrWhiteSpace ( _l oginInfo . LotNo ) ? "00" : ( _l oginInfo . LotNo . Trim ( ) . Length < 2 ? _l oginInfo . LotNo . Trim ( ) . PadLeft ( 2 , '0' ) : _l oginInfo . LotNo . Trim ( ) ) ;
string markedSerial = string . Format ( "{0}{1:D4}" , lotNo , seq ) ;
string markedSerial = string . Format ( "{0}{1}{2:D4}" , yymmdd , lotNo , seq ) ;
string drawingNo = _ markingConfig . DrawingNo ;
string drawingNo = _ markingConfig . DrawingNo ;
string hkmcNo = _ markingConfig . HkmcNo ;
string hkmcNo = _ markingConfig . HkmcNo ;
string qrString = string . Format ( "{0};{1};{2};{3};" , serial , drawingNo , hkmcNo , markedSerial ) ;
string qrString = string . Format ( "{0};{1};{2};{3};" , serial , drawingNo , hkmcNo , markedSerial ) ;
@ -1149,7 +1401,7 @@ namespace marking_gui
}
}
// UI 스레드로 안전하게 컴백
// UI 스레드로 안전하게 컴백
Dispatcher . Invoke ( ( ) = >
await Dispatcher . InvokeAsync ( async ( ) = >
{
{
if ( txtMarkedSerial ! = null )
if ( txtMarkedSerial ! = null )
{
{
@ -1188,7 +1440,7 @@ namespace marking_gui
LoggerService . Info ( $"[QR 검증 성공] S/N: {serial}, 각인번호: {markedSerial} -> DB 저장 진행" ) ;
LoggerService . Info ( $"[QR 검증 성공] S/N: {serial}, 각인번호: {markedSerial} -> DB 저장 진행" ) ;
// QR 검증 통과 시 최종 DB 저장 및 완료 처리
// QR 검증 통과 시 최종 DB 저장 및 완료 처리
SaveAndFinalize ( serial , finalVoltage , finalCurrent , finalResistance , true , "OK" , markedSerial ) ;
await SaveAndFinalizeAsync ( serial , finalVoltage , finalCurrent , finalResistance , true , "OK" , markedSerial , bypassPrevSteps ) ;
}
}
else
else
{
{
@ -1197,7 +1449,7 @@ namespace marking_gui
LoggerService . Warn ( $"[QR 검증 실패] S/N: {serial}, 각인번호: {markedSerial} -> DB NG 저장 진행" ) ;
LoggerService . Warn ( $"[QR 검증 실패] S/N: {serial}, 각인번호: {markedSerial} -> DB NG 저장 진행" ) ;
// 마킹 성공 후 QR 검증 불일치/취소 시 NG로 DB 저장
// 마킹 성공 후 QR 검증 불일치/취소 시 NG로 DB 저장
SaveAndFinalize ( serial , finalVoltage , finalCurrent , finalResistance , false , "NG" , markedSerial ) ;
await SaveAndFinalizeAsync ( serial , finalVoltage , finalCurrent , finalResistance , false , "NG" , markedSerial , bypassPrevSteps ) ;
}
}
}
}
else
else
@ -1240,6 +1492,11 @@ namespace marking_gui
}
}
} ) ;
} ) ;
}
}
catch ( OperationCanceledException )
{
LoggerService . Warn ( "[검사 중지] 마킹 단계에서 중지 요청이 수신되었습니다." ) ;
// CancelInspectionSequenceAsync가 장비 복구 및 UI 정리를 담당
}
catch ( Exception threadEx )
catch ( Exception threadEx )
{
{
Dispatcher . Invoke ( ( ) = >
Dispatcher . Invoke ( ( ) = >
@ -1265,25 +1522,28 @@ namespace marking_gui
txtSerial . IsEnabled = true ;
txtSerial . IsEnabled = true ;
} ) ;
} ) ;
}
}
} ) ;
} , token ) ;
}
}
else
else
{
{
// 불합격인 경우 즉시 DB 저장 및 복구
// 불합격인 경우 즉시 DB 저장 및 복구
SaveAndFinalize ( serial , finalVoltage , finalCurrent , finalResistance , isFinalPass , "NG" , "" ) ;
await SaveAndFinalizeAsync ( serial , finalVoltage , finalCurrent , finalResistance , isFinalPass , "NG" , "" , bypassPrevSteps ) ;
}
}
}
}
}
}
private async Task < ( double Voltage , double Current , double Resistance ) > ReadMeasurementFromDeviceAsync ( )
private async Task < ( double Voltage , double Current , double Resistance ) > ReadMeasurementFromDeviceAsync ( CancellationToken token = default )
{
{
try
try
{
{
token . ThrowIfCancellationRequested ( ) ;
// EquipmentMeasurementService 를 통해 실시간 계측값을 획득하는 즉시 GUI에 반영
// EquipmentMeasurementService 를 통해 실시간 계측값을 획득하는 즉시 GUI에 반영
var ( voltage , current , resistance ) = await _ equipment . MeasureAsync ( ( v , c , r ) = >
var ( voltage , current , resistance ) = await _ equipment . MeasureAsync ( ( v , c , r ) = >
{
{
Dispatcher . Invoke ( ( ) = >
Dispatcher . Invoke ( ( ) = >
{
{
if ( token . IsCancellationRequested ) return ; // 취소됐으면 UI 갱신 스킵
if ( v . HasValue )
if ( v . HasValue )
{
{
txtVoltage . Text = v . Value . ToString ( "F2" ) ;
txtVoltage . Text = v . Value . ToString ( "F2" ) ;
@ -1301,6 +1561,11 @@ namespace marking_gui
return ( voltage , current , resistance ) ;
return ( voltage , current , resistance ) ;
}
}
catch ( OperationCanceledException )
{
// 취소 예외는 호출자(RunInspectionSequenceAsync)로 전파하여 일관된 중지 처리
throw ;
}
catch ( Exception ex )
catch ( Exception ex )
{
{
LoggerService . Error ( "[계측기] 측정 중 오류 발생" , ex ) ;
LoggerService . Error ( "[계측기] 측정 중 오류 발생" , ex ) ;
@ -1321,7 +1586,7 @@ namespace marking_gui
}
}
}
}
private async void SaveAndFinalize ( string serial , double voltage , double current , double resistance , bool isFinalPass , string result , string markedSerial )
private async Task SaveAndFinalizeAsync ( string serial , double voltage , double current , double resistance , bool isFinalPass , string result , string markedSerial , bool bypassPrevSteps = false )
{
{
// 종료 시간 기록 및 UI 갱신
// 종료 시간 기록 및 UI 갱신
_ testEndTime = DateTime . Now ;
_ testEndTime = DateTime . Now ;
@ -1333,6 +1598,16 @@ namespace marking_gui
txtProgressStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 5 9 , 1 3 0 , 2 4 6 ) ) ; // Blue
txtProgressStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 5 9 , 1 3 0 , 2 4 6 ) ) ; // Blue
}
}
// 강제검사(BYPASS) 시 또는 PCB 바코드가 없는 경우 시리얼 번호(serial)를 PCB 바코드로 대입
string pcbBarcodeToSave = ( bypassPrevSteps | | _ currentProduct = = null | | string . IsNullOrWhiteSpace ( _ currentProduct . PcbBarcode ) )
? serial
: _ currentProduct . PcbBarcode ;
if ( bypassPrevSteps & & _ currentProduct ! = null )
{
_ currentProduct . PcbBarcode = serial ;
}
try
try
{
{
// 데이터베이스 저장 (비동기 스레드 격리로 UI 프리징 방지)
// 데이터베이스 저장 (비동기 스레드 격리로 UI 프리징 방지)
@ -1355,7 +1630,7 @@ namespace marking_gui
_ testStartTime ,
_ testStartTime ,
_ testEndTime ,
_ testEndTime ,
markedSerial ,
markedSerial ,
_ currentProduct ? . P cbBarcode, // Housing_Assembly 테이블에서 조회한 PCB 바코드
p cbBarcodeToSav e, // 강제검사 시 시리얼 번호 사용
_ markingConfig . DrawingNo ,
_ markingConfig . DrawingNo ,
_ markingConfig . HkmcNo ) ;
_ markingConfig . HkmcNo ) ;
} ) ;
} ) ;
@ -1364,8 +1639,33 @@ namespace marking_gui
{
{
LoggerService . Info ( $"[DB 저장 완료] S/N: {serial}, 결과: {result}, V: {voltage:F2}, I: {current:F2}, R: {resistance:F2}" ) ;
LoggerService . Info ( $"[DB 저장 완료] S/N: {serial}, 결과: {result}, V: {voltage:F2}, I: {current:F2}, R: {resistance:F2}" ) ;
// 스캔 검증까지 포함한 최종 판정 결과 카드 갱신
if ( isFinalPass & & ( result = = "PASS" | | result = = "OK" ) )
{
txtFinalResult . Text = "OK" ;
txtFinalResult . Foreground = new SolidColorBrush ( Color . FromRgb ( 1 6 , 1 8 5 , 1 2 9 ) ) ; // Green text
if ( borderVerdictCard ! = null )
{
borderVerdictCard . Background = new SolidColorBrush ( Color . FromRgb ( 2 4 0 , 2 5 3 , 2 5 0 ) ) ; // Light Green
borderVerdictCard . BorderBrush = new SolidColorBrush ( Color . FromRgb ( 1 6 , 1 8 5 , 1 2 9 ) ) ; // Green border
borderVerdictCard . BorderThickness = new Thickness ( 3 ) ;
}
txtSystemStatus . Text = "상태: 모든 공정 검사 및 QR 스캔 합격" ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 1 6 , 1 8 5 , 1 2 9 ) ) ;
}
else
{
txtFinalResult . Text = "NG" ;
txtFinalResult . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 3 9 , 6 8 , 6 8 ) ) ; // Red text
if ( borderVerdictCard ! = null )
{
borderVerdictCard . Background = new SolidColorBrush ( Color . FromRgb ( 2 5 4 , 2 4 2 , 2 4 2 ) ) ; // Light Red
borderVerdictCard . BorderBrush = new SolidColorBrush ( Color . FromRgb ( 2 3 9 , 6 8 , 6 8 ) ) ; // Red border
borderVerdictCard . BorderThickness = new Thickness ( 3 ) ;
}
txtSystemStatus . Text = ( result = = "NG" ) ? "상태: 각인 QR 검증 불합격 (NG)" : "상태: 검사 불합격" ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 3 9 , 6 8 , 6 8 ) ) ;
}
if ( txtProgressStatus ! = null )
if ( txtProgressStatus ! = null )
{
{
@ -1469,7 +1769,12 @@ namespace marking_gui
_l oginInfo = loginWindow . Result ;
_l oginInfo = loginWindow . Result ;
DisplayLoginInfo ( ) ;
DisplayLoginInfo ( ) ;
ResetUI ( ) ;
ResetUI ( ) ;
InitializeMarkingDevice ( ) ;
// UI 스레드가 멈추지 않고 비동기로 마킹 장치 초기화 진행
Dispatcher . InvokeAsync ( ( ) = >
{
InitializeMarkingDevice ( ) ;
} , System . Windows . Threading . DispatcherPriority . Background ) ;
// 로그인 완료 후 보드 상시전원(Channel 2) 인가를 완전 비동기 백그라운드 스레드로 실행 (UI 프리징 100% 방지)
// 로그인 완료 후 보드 상시전원(Channel 2) 인가를 완전 비동기 백그라운드 스레드로 실행 (UI 프리징 100% 방지)
_ = System . Threading . Tasks . Task . Run ( async ( ) = >
_ = System . Threading . Tasks . Task . Run ( async ( ) = >
@ -1575,16 +1880,23 @@ namespace marking_gui
_d bWasOffline = true ;
_d bWasOffline = true ;
LoggerService . Warn ( "[DB 재연결 감시] DB 연결 끊김 감지" ) ;
LoggerService . Warn ( "[DB 재연결 감시] DB 연결 끊김 감지" ) ;
Dispatcher . Invoke ( ( ) = >
if ( ! _ isInspectionRunning )
{
{
if ( txtSystemStatus ! = null )
Dispatcher . Invoke ( ( ) = >
{
{
txtSystemStatus . Text = "DB 연결 끊김 - 재연결 대기 중..." ;
SetStartButtonConnecting ( "⚠️ DB 연결 필요" ) ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 4 5 , 1 5 8 , 1 1 ) ) ;
}
if ( txtSystemStatus ! = null )
if ( txtNextSequence ! = null )
{
txtNextSequence . Text = "연결 끊김" ;
txtSystemStatus . Text = "DB 연결 끊김 - 재연결 대기 중..." ;
} ) ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 4 5 , 1 5 8 , 1 1 ) ) ;
}
if ( txtNextSequence ! = null )
txtNextSequence . Text = "연결 끊김" ;
if ( btnRefreshSequence ! = null )
btnRefreshSequence . Visibility = Visibility . Visible ;
} ) ;
}
}
}
else if ( isNowConnected & & _d bWasOffline )
else if ( isNowConnected & & _d bWasOffline )
{
{
@ -1592,17 +1904,27 @@ namespace marking_gui
_d bWasOffline = false ;
_d bWasOffline = false ;
LoggerService . Info ( "[DB 재연결 감시] DB 재연결 감지 - 순번 자동 갱신" ) ;
LoggerService . Info ( "[DB 재연결 감시] DB 재연결 감지 - 순번 자동 갱신" ) ;
Dispatcher . Invoke ( ( ) = >
if ( ! _ isInspectionRunning )
{
{
if ( txtSystemStatus ! = null )
Dispatcher . Invoke ( ( ) = >
{
{
txtSystemStatus . Text = "DB 재연결 완료 - 각인 순번 갱신 중..." ;
if ( txtSystemStatus ! = null )
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 1 6 , 1 8 5 , 1 2 9 ) ) ;
{
}
txtSystemStatus . Text = "DB 재연결 완료 - 각인 순번 갱신 중..." ;
} ) ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 1 6 , 1 8 5 , 1 2 9 ) ) ;
}
if ( btnRefreshSequence ! = null )
btnRefreshSequence . Visibility = Visibility . Collapsed ;
} ) ;
// UI 스레드에서 순번 갱신 (비동기 안전)
// UI 스레드에서 순번 갱신 (비동기 안전)
await Dispatcher . InvokeAsync ( ( ) = > RefreshNextSequence ( ) ) ;
await Dispatcher . InvokeAsync ( ( ) = > RefreshNextSequence ( ) ) ;
}
}
else if ( isNowConnected & & ! _d bWasOffline & & ! _ isInspectionRunning )
{
// DB 연결 유지 상태에서도 DB 레코드 직접 변경/삭제 시 각인 순번 실시간 반영 (조용히 UI 갱신)
await Dispatcher . InvokeAsync ( ( ) = > RefreshNextSequenceSilently ( ) ) ;
}
}
}
}
catch ( Exception ex )
catch ( Exception ex )
@ -1663,6 +1985,14 @@ namespace marking_gui
// 도안 파일 선택 버튼 클릭
// 도안 파일 선택 버튼 클릭
private void btnSelectEzd_Click ( object sender , RoutedEventArgs e )
private void btnSelectEzd_Click ( object sender , RoutedEventArgs e )
{
{
if ( ! CheckUserLoggedIn ( ) ) return ;
if ( _ isInspectionRunning )
{
MessageBox . Show ( "현재 검사가 진행 중입니다.\n검사가 완료되거나 중지된 후 도안 파일을 변경하십시오." , "도안 변경 불가" , MessageBoxButton . OK , MessageBoxImage . Warning ) ;
return ;
}
try
try
{
{
Microsoft . Win32 . OpenFileDialog openFileDialog = new Microsoft . Win32 . OpenFileDialog ( ) ;
Microsoft . Win32 . OpenFileDialog openFileDialog = new Microsoft . Win32 . OpenFileDialog ( ) ;
@ -1761,8 +2091,11 @@ namespace marking_gui
if ( txtEzdFile ! = null ) txtEzdFile . Text = fileName ;
if ( txtEzdFile ! = null ) txtEzdFile . Text = fileName ;
string modelName = System . IO . Path . GetFileNameWithoutExtension ( fileName ) ;
string modelName = System . IO . Path . GetFileNameWithoutExtension ( fileName ) ;
if ( _l oginInfo ! = null ) _l oginInfo . Model = modelName ;
if ( _l oginInfo = = null | | string . IsNullOrWhiteSpace ( _l oginInfo . Model ) | | _l oginInfo . Model = = "---" )
if ( txtModelInfo ! = null ) txtModelInfo . Text = modelName ;
{
if ( _l oginInfo ! = null ) _l oginInfo . Model = modelName ;
if ( txtModelInfo ! = null ) txtModelInfo . Text = modelName ;
}
if ( saveToConfig )
if ( saveToConfig )
{
{
@ -1806,6 +2139,14 @@ namespace marking_gui
return ;
return ;
}
}
if ( txtSystemStatus ! = null & & ! _d atabase . OfflineMode )
{
txtSystemStatus . Text = "DB 연결 시도 및 순번 조회 중..." ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 5 9 , 1 3 0 , 2 4 6 ) ) ; // Blue
}
bool sequenceFetched = false ;
try
try
{
{
int nextSeq = await GetNextMarkingSequenceAsync ( _l oginInfo . LotNo ) ;
int nextSeq = await GetNextMarkingSequenceAsync ( _l oginInfo . LotNo ) ;
@ -1814,6 +2155,20 @@ namespace marking_gui
txtNextSequence . Text = string . Format ( "{0:D4} 차례" , nextSeq ) ;
txtNextSequence . Text = string . Format ( "{0:D4} 차례" , nextSeq ) ;
}
}
if ( txtSystemStatus ! = null & & ! _d atabase . OfflineMode )
{
txtSystemStatus . Text = "상태: DB 연결 완료 (정상)" ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 1 6 , 1 8 5 , 1 2 9 ) ) ; // Green
}
// 정상 조회 성공 시 재연결 버튼 숨김
if ( btnRefreshSequence ! = null )
{
btnRefreshSequence . Visibility = Visibility . Collapsed ;
}
sequenceFetched = true ;
if ( nextSeq > 9 9 9 9 )
if ( nextSeq > 9 9 9 9 )
{
{
if ( borderErrorBanner ! = null ) borderErrorBanner . Visibility = Visibility . Visible ;
if ( borderErrorBanner ! = null ) borderErrorBanner . Visibility = Visibility . Visible ;
@ -1832,13 +2187,122 @@ namespace marking_gui
}
}
catch ( Exception ex )
catch ( Exception ex )
{
{
_d bWasOffline = true ;
LoggerService . Error ( "[UI RefreshSequence] 순번 갱신 중 에러" , ex ) ;
LoggerService . Error ( "[UI RefreshSequence] 순번 갱신 중 에러" , ex ) ;
if ( txtNextSequence ! = null ) txtNextSequence . Text = "조회 실패" ;
if ( txtNextSequence ! = null ) txtNextSequence . Text = "조회 실패" ;
if ( txtSystemStatus ! = null )
if ( txtSystemStatus ! = null )
{
{
txtSystemStatus . Text = $"각인 순번 조회 실패 : {ex.Message}" ;
txtSystemStatus . Text = $"DB 연결 실패 / 순번 조회 에러 : {ex.Message}" ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 3 9 , 6 8 , 6 8 ) ) ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 3 9 , 6 8 , 6 8 ) ) ;
}
}
// DB 연결/조회 실패 시 재연결 버튼 표기
if ( btnRefreshSequence ! = null )
{
btnRefreshSequence . Visibility = Visibility . Visible ;
}
}
finally
{
if ( sequenceFetched )
{
_ isInitialLoading = false ;
_d bWasOffline = false ;
SetStartButtonReady ( ) ; // 조회가 무사히 성공했을 때만 스타트 버튼 활성화!
}
else
{
_d bWasOffline = true ;
SetStartButtonConnecting ( "⚠️ DB 연결 필요" ) ; // 실패 시 계속 비활성화 유지
}
}
}
/// <summary>
/// DB 레코드가 수동으로 삭제/수정되었을 때 UI 상태창을 깜빡이지 않고 화면 각인순번만 조용히 실시간 자동 동기화
/// </summary>
private async void RefreshNextSequenceSilently ( )
{
if ( _ isInspectionRunning | | _l oginInfo = = null | | string . IsNullOrEmpty ( _l oginInfo . LotNo ) )
return ;
try
{
int nextSeq = await GetNextMarkingSequenceAsync ( _l oginInfo . LotNo ) ;
if ( txtNextSequence ! = null )
{
txtNextSequence . Text = string . Format ( "{0:D4} 차례" , nextSeq ) ;
}
}
catch { }
}
private async void btnRefreshSequence_Click ( object sender , RoutedEventArgs e )
{
SetStartButtonConnecting ( ) ;
if ( btnRefreshSequence ! = null )
{
btnRefreshSequence . IsEnabled = false ;
btnRefreshSequence . Content = "⏳ 연결 시도 중..." ;
btnRefreshSequence . UpdateLayout ( ) ;
// UI 렌더링 강제 갱신으로 작업자 피드백 즉시 전파
Dispatcher . Invoke ( ( ) = > { } , System . Windows . Threading . DispatcherPriority . Render ) ;
}
if ( txtSystemStatus ! = null )
{
txtSystemStatus . Text = "DB 연결 시도 중..." ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 5 9 , 1 3 0 , 2 4 6 ) ) ; // Blue
}
if ( txtNextSequence ! = null )
{
txtNextSequence . Text = "조회 중..." ;
}
try
{
string dbErrMsg = string . Empty ;
bool isConnected = await Task . Run ( ( ) = > _d atabase . CheckConnection ( out dbErrMsg ) ) ;
if ( isConnected )
{
_d bWasOffline = false ;
if ( btnRefreshSequence ! = null ) btnRefreshSequence . Visibility = Visibility . Collapsed ;
RefreshNextSequence ( ) ;
}
else
{
_d bWasOffline = true ;
if ( txtNextSequence ! = null ) txtNextSequence . Text = "연결 실패" ;
if ( btnRefreshSequence ! = null ) btnRefreshSequence . Visibility = Visibility . Visible ;
if ( txtSystemStatus ! = null )
{
txtSystemStatus . Text = string . IsNullOrWhiteSpace ( dbErrMsg )
? "DB 연결 실패 - 네트워크 및 DB 설정 확인 필요"
: $"DB 연결 실패: {dbErrMsg}" ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 3 9 , 6 8 , 6 8 ) ) ; // Red
}
}
}
catch ( Exception ex )
{
if ( txtNextSequence ! = null ) txtNextSequence . Text = "오류 발생" ;
if ( btnRefreshSequence ! = null ) btnRefreshSequence . Visibility = Visibility . Visible ;
if ( txtSystemStatus ! = null )
{
txtSystemStatus . Text = $"DB 재연결 오류: {ex.Message}" ;
txtSystemStatus . Foreground = new SolidColorBrush ( Color . FromRgb ( 2 3 9 , 6 8 , 6 8 ) ) ; // Red
}
}
finally
{
if ( btnRefreshSequence ! = null )
{
btnRefreshSequence . IsEnabled = true ;
btnRefreshSequence . Content = "🔄 DB 재연결" ;
}
SetStartButtonReady ( ) ;
}
}
}
}
}
}