You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
495 lines
24 KiB
495 lines
24 KiB
using System;
|
|
using System.Configuration;
|
|
using marking_gui.Models;
|
|
|
|
namespace marking_gui.Services
|
|
{
|
|
public class DatabaseService
|
|
{
|
|
private readonly string _connectionString;
|
|
private readonly bool _offlineMode = false;
|
|
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, int> _offlineLotSequences = new System.Collections.Concurrent.ConcurrentDictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
public bool OfflineMode => _offlineMode;
|
|
|
|
public DatabaseService()
|
|
{
|
|
try
|
|
{
|
|
string dbServer = string.Empty;
|
|
string dbName = string.Empty;
|
|
string dbUser = string.Empty;
|
|
string dbPassword = string.Empty;
|
|
string rawConnStr = string.Empty;
|
|
|
|
// AppDomain.CurrentDomain.BaseDirectory 에서 Database.ini 로드 시도
|
|
string iniPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Database.ini");
|
|
if (System.IO.File.Exists(iniPath))
|
|
{
|
|
var lines = System.IO.File.ReadAllLines(iniPath);
|
|
foreach (var line in lines)
|
|
{
|
|
var trimmed = line.Trim();
|
|
if (trimmed.StartsWith(";") || trimmed.StartsWith("#"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (trimmed.StartsWith("ConnectionString", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var parts = trimmed.Split(new[] { '=' }, 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
rawConnStr = parts[1].Trim();
|
|
}
|
|
}
|
|
else if (trimmed.StartsWith("Db_Server", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var parts = trimmed.Split(new[] { '=' }, 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
dbServer = parts[1].Trim();
|
|
}
|
|
}
|
|
else if (trimmed.StartsWith("Db_Name", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var parts = trimmed.Split(new[] { '=' }, 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
dbName = parts[1].Trim();
|
|
}
|
|
}
|
|
else if (trimmed.StartsWith("Db_User", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var parts = trimmed.Split(new[] { '=' }, 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
dbUser = parts[1].Trim();
|
|
}
|
|
}
|
|
else if (trimmed.StartsWith("Db_Password", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var parts = trimmed.Split(new[] { '=' }, 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
dbPassword = parts[1].Trim();
|
|
}
|
|
}
|
|
else if (trimmed.StartsWith("OfflineMode", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var parts = trimmed.Split(new[] { '=' }, 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
bool.TryParse(parts[1].Trim(), out _offlineMode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 개별 설정이 있는 경우 조합해서 사용
|
|
if (!string.IsNullOrWhiteSpace(dbServer) &&
|
|
!string.IsNullOrWhiteSpace(dbName) &&
|
|
!string.IsNullOrWhiteSpace(dbUser) &&
|
|
!string.IsNullOrWhiteSpace(dbPassword))
|
|
{
|
|
string cleanServer = dbServer.Trim().Replace(" ", "");
|
|
_connectionString = $"Server={cleanServer};Database={dbName};User Id={dbUser};Password={dbPassword};";
|
|
}
|
|
else
|
|
{
|
|
_connectionString = rawConnStr;
|
|
}
|
|
|
|
// Database.ini 에 없을 경우 App.config 백업 사용
|
|
if (string.IsNullOrEmpty(_connectionString))
|
|
{
|
|
var connSettings = ConfigurationManager.ConnectionStrings["MssqlConnection"];
|
|
if (connSettings != null)
|
|
{
|
|
_connectionString = connSettings.ConnectionString;
|
|
}
|
|
else
|
|
{
|
|
_connectionString = "Server=localhost;Database=MarkingDb;Trusted_Connection=True;";
|
|
}
|
|
}
|
|
|
|
// SSL 인증서 체인 신뢰성 오류(TrustServerCertificate=True 누락) 방지 조치 추가
|
|
if (!string.IsNullOrWhiteSpace(_connectionString) && _connectionString.IndexOf("TrustServerCertificate", StringComparison.OrdinalIgnoreCase) < 0)
|
|
{
|
|
if (!_connectionString.EndsWith(";")) _connectionString += ";";
|
|
_connectionString += "TrustServerCertificate=True;";
|
|
}
|
|
|
|
// DB 서버 오프라인 시 지연 방지를 위해 연결 타임아웃(Connection Timeout)을 3초로 조정
|
|
if (!string.IsNullOrWhiteSpace(_connectionString) &&
|
|
_connectionString.IndexOf("Connect Timeout", StringComparison.OrdinalIgnoreCase) < 0 &&
|
|
_connectionString.IndexOf("Connection Timeout", StringComparison.OrdinalIgnoreCase) < 0)
|
|
{
|
|
if (!_connectionString.EndsWith(";")) _connectionString += ";";
|
|
_connectionString += "Connect Timeout=3;";
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_connectionString = string.Empty;
|
|
System.Diagnostics.Debug.WriteLine("설정 로드 중 예외 발생: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
public bool CheckConnection()
|
|
{
|
|
return CheckConnection(out _);
|
|
}
|
|
|
|
public bool CheckConnection(out string errorMessage)
|
|
{
|
|
errorMessage = string.Empty;
|
|
if (_offlineMode)
|
|
return true;
|
|
|
|
if (string.IsNullOrWhiteSpace(_connectionString))
|
|
{
|
|
errorMessage = "데이터베이스 연결 문자열이 설정되지 않았습니다. (Database.ini 파일 점검 필요)";
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
using (var conn = new System.Data.SqlClient.SqlConnection(_connectionString))
|
|
{
|
|
conn.Open();
|
|
return true;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorMessage = ex.Message;
|
|
System.Diagnostics.Debug.WriteLine("[DB CheckConnection] 연결 실패: " + ex.Message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public Product GetProductBySerial(string serial)
|
|
{
|
|
if (string.IsNullOrEmpty(serial))
|
|
return null;
|
|
|
|
string searchSerial = serial.Trim().ToUpper();
|
|
|
|
// 오프라인 모드인 경우 데이터베이스가 없으므로 항상 이전 공정 통과(PASS)로 가정하여 가상 데이터 반환
|
|
if (_offlineMode)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine("[OfflineMode] 제품 조회 우회 (오프라인 모드 활성화). S/N: " + searchSerial);
|
|
return new Product(searchSerial, "PASS", "PASS", "PASS");
|
|
}
|
|
|
|
try
|
|
{
|
|
// 이전 3개 공정 테이블(Housing_Assembly, Cal_EOL_Result, EOL)에서 IC_SN 기준으로 Result 각각 조회
|
|
// Housing_Assembly 테이블에서 PCB_Barcode 가져옴
|
|
string query = @"
|
|
SELECT
|
|
(SELECT TOP 1 [Result] FROM [dbo].[Housing_Assembly] WHERE UPPER([IC_SN]) = @Serial) AS HousingResult,
|
|
(SELECT TOP 1 [Final_Result] FROM [dbo].[Cal_EOL_Result] WHERE UPPER([IC_SN]) = @Serial) AS CalResult,
|
|
(SELECT TOP 1 [Result] FROM [dbo].[EOL] WHERE UPPER([IC_SN]) = @Serial) AS EolResult,
|
|
(SELECT TOP 1 [PCB_Barcode] FROM [dbo].[Housing_Assembly] WHERE UPPER([IC_SN]) = @Serial) AS PcbBarcode";
|
|
|
|
using (var conn = new System.Data.SqlClient.SqlConnection(_connectionString))
|
|
{
|
|
using (var cmd = new System.Data.SqlClient.SqlCommand(query, conn))
|
|
{
|
|
cmd.CommandTimeout = 5; // DB 응답 대기 최대 5초
|
|
cmd.Parameters.AddWithValue("@Serial", searchSerial);
|
|
conn.Open();
|
|
|
|
using (var reader = cmd.ExecuteReader())
|
|
{
|
|
if (reader.Read())
|
|
{
|
|
// 데이터베이스 값이 NULL 이거나 빈 값일 경우 "대기"로 처리
|
|
string housingRes = reader["HousingResult"] != DBNull.Value ? reader["HousingResult"].ToString().Trim() : "대기";
|
|
string calRes = reader["CalResult"] != DBNull.Value ? reader["CalResult"].ToString().Trim() : "대기";
|
|
string eolRes = reader["EolResult"] != DBNull.Value ? reader["EolResult"].ToString().Trim() : "대기";
|
|
string pcbBarcode = reader["PcbBarcode"] != DBNull.Value ? reader["PcbBarcode"].ToString().Trim() : string.Empty;
|
|
|
|
// 세 공정 중 하나라도 데이터 흔적이 있는 경우 Product 객체 생성 반환
|
|
if (housingRes != "대기" || calRes != "대기" || eolRes != "대기")
|
|
{
|
|
return new Product(searchSerial, housingRes, calRes, eolRes)
|
|
{
|
|
PcbBarcode = pcbBarcode
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine("[DB GetProduct] 이전 공정 결과 조회 중 에러: " + ex.Message);
|
|
throw;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// [dbo].[Marking] 테이블에 해당 IC S/N 데이터가 이미 존재하는지 중복 검사를 수행합니다.
|
|
/// </summary>
|
|
public bool IsProductExists(string serial)
|
|
{
|
|
if (_offlineMode || string.IsNullOrWhiteSpace(serial))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
string query = "SELECT COUNT(1) FROM [dbo].[Marking] WHERE [IC_SN] = @Serial;";
|
|
using (var conn = new System.Data.SqlClient.SqlConnection(_connectionString))
|
|
{
|
|
using (var cmd = new System.Data.SqlClient.SqlCommand(query, conn))
|
|
{
|
|
cmd.CommandTimeout = 3;
|
|
cmd.Parameters.AddWithValue("@Serial", serial.Trim());
|
|
conn.Open();
|
|
int count = (int)cmd.ExecuteScalar();
|
|
return count > 0;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine("[DB IsProductExists] 중복 조회 중 예외: " + ex.Message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 최종 성능 검출 값을 데이터베이스에 저장합니다.
|
|
/// 세 계측값은 현장 테이블의 DECIMAL(5, 2)에 맞춰 저장합니다 (Overload: 999.00).
|
|
/// </summary>
|
|
public bool SaveProductResult(
|
|
string serial,
|
|
string maker,
|
|
string model,
|
|
string variant1,
|
|
string variant2,
|
|
string operatorId,
|
|
string line,
|
|
string lotNo,
|
|
string jigNo,
|
|
double voltage,
|
|
double current,
|
|
double resistance,
|
|
string result,
|
|
DateTime startTime,
|
|
DateTime endTime,
|
|
string markedSerial,
|
|
string pcbBarcode = null,
|
|
string drawingNo = "",
|
|
string hkmcNo = "")
|
|
{
|
|
// Overload NG follows the same no-save rule as every other NG, even for direct callers.
|
|
if (result != "PASS" && result != "OK")
|
|
{
|
|
LoggerService.Info($"[DB 저장 생략] S/N: {serial}, 결과: {result} (NG는 저장하지 않음)");
|
|
return false;
|
|
}
|
|
|
|
var measurementParameters = CreateMeasurementParameters(voltage, current, resistance);
|
|
decimal roundedVoltage = (decimal)measurementParameters[0].Value;
|
|
decimal roundedCurrent = (decimal)measurementParameters[1].Value;
|
|
decimal roundedResistance = (decimal)measurementParameters[2].Value;
|
|
|
|
// IC_SN 및 PCB_Barcode 컬럼 규격(NVARCHAR(30), NOT NULL) 방어 처리
|
|
if (string.IsNullOrWhiteSpace(serial))
|
|
{
|
|
serial = "UNKNOWN_" + DateTime.Now.ToString("HHmmss");
|
|
}
|
|
if (serial.Length > 30)
|
|
{
|
|
serial = serial.Substring(0, 30);
|
|
}
|
|
string safeResult = "OK";
|
|
|
|
// DB 컬럼 길이 한계에 맞춰서 문자열 안전하게 자르기 (Truncate)
|
|
string safeMaker = string.IsNullOrEmpty(maker) ? "" : (maker.Length > 10 ? maker.Substring(0, 10) : maker);
|
|
string safeModel = string.IsNullOrEmpty(model) ? "" : (model.Length > 10 ? model.Substring(0, 10) : model);
|
|
string safeVariant1 = string.IsNullOrEmpty(variant1) ? "" : (variant1.Length > 10 ? variant1.Substring(0, 10) : variant1);
|
|
string safeVariant2 = string.IsNullOrEmpty(variant2) ? "" : (variant2.Length > 10 ? variant2.Substring(0, 10) : variant2);
|
|
string safeOperator = string.IsNullOrEmpty(operatorId) ? "" : (operatorId.Length > 10 ? operatorId.Substring(0, 10) : operatorId);
|
|
string safeLine = string.IsNullOrEmpty(line) ? "" : (line.Length > 10 ? line.Substring(0, 10) : line);
|
|
string safeLotNo = string.IsNullOrEmpty(lotNo) ? "" : (lotNo.Length > 10 ? lotNo.Substring(0, 10) : lotNo);
|
|
string safeJigNo = string.IsNullOrEmpty(jigNo) ? "" : (jigNo.Length > 10 ? jigNo.Substring(0, 10) : jigNo);
|
|
|
|
DateTime prodDate = endTime;
|
|
// QR Code 조합 포맷 생성 ([IC S/N];[도면품번];[HKMC품번];[제품 S/N];) (Marking_QR: NVARCHAR(60))
|
|
string markingQr = string.Format("{0};{1};{2};{3};", serial, drawingNo ?? "", hkmcNo ?? "", markedSerial);
|
|
if (markingQr.Length > 60)
|
|
{
|
|
markingQr = markingQr.Substring(0, 60);
|
|
}
|
|
|
|
// 오프라인 모드인 경우 실제 DB 저장을 생략하고 정상 통과 처리
|
|
if (_offlineMode)
|
|
{
|
|
if (safeResult == "OK" && !string.IsNullOrWhiteSpace(safeLotNo) && !string.IsNullOrWhiteSpace(markedSerial) && markedSerial.Length >= 4)
|
|
{
|
|
string seqStr = markedSerial.Substring(markedSerial.Length - 4);
|
|
if (int.TryParse(seqStr, out int seqNum))
|
|
{
|
|
_offlineLotSequences.AddOrUpdate(safeLotNo, seqNum, (key, oldVal) => Math.Max(oldVal, seqNum));
|
|
}
|
|
}
|
|
|
|
System.Diagnostics.Debug.WriteLine(string.Format(
|
|
"[OfflineMode] DB 저장 생략 (오프라인 모드 활성화) S/N: {0} | 각인번호: {1} | QR: {2} | Maker: {3} | Model: {4} | Line: {5} | LotNo: {6} | Vol_1: {7:F2}V | Cur_1: {8:F2}mA | Res_1: {9:F3}kΩ | 결과: {10} | 시작:{11} | 종료:{12}",
|
|
serial, markedSerial, markingQr, safeMaker, safeModel, safeLine, safeLotNo, roundedVoltage, roundedCurrent, roundedResistance, result,
|
|
startTime.ToString("HH:mm:ss"), endTime.ToString("HH:mm:ss")));
|
|
return true;
|
|
}
|
|
|
|
string safePcbBarcode = string.IsNullOrWhiteSpace(pcbBarcode) ? serial : pcbBarcode.Trim();
|
|
if (safePcbBarcode.Length > 30)
|
|
{
|
|
safePcbBarcode = safePcbBarcode.Substring(0, 30);
|
|
}
|
|
|
|
// 실제 MSSQL DB 적재 실행 (동일 IC_SN 재검사 시 데이터가 덮어씌워지도록 MERGE 문 도입)
|
|
try
|
|
{
|
|
string query = @"MERGE INTO [dbo].[Marking] AS target
|
|
USING (SELECT @Serial AS IC_SN) AS source
|
|
ON (target.[IC_SN] = source.IC_SN)
|
|
WHEN MATCHED THEN
|
|
UPDATE SET
|
|
[PCB_Barcode] = @PcbBarcode,
|
|
[Maker] = @Maker,
|
|
[Model] = @Model,
|
|
[Variant_1] = @Var1,
|
|
[Variant_2] = @Var2,
|
|
[Operator] = @Operator,
|
|
[Production_Date] = @ProdDate,
|
|
[Line] = @Line,
|
|
[Lot_No] = @LotNo,
|
|
[Jig_No] = @JigNo,
|
|
[PT_Vol_1] = @Vol1,
|
|
[PT_Current_1] = @Cur1,
|
|
[PT_Resistance_1] = @Res1,
|
|
[Marking_QR] = @Qr,
|
|
[Result] = @Result
|
|
WHEN NOT MATCHED THEN
|
|
INSERT ([IC_SN], [PCB_Barcode], [Marking_QR], [Maker], [Model], [Variant_1], [Variant_2], [Operator], [Production_Date], [Line], [Lot_No], [Jig_No], [PT_Vol_1], [PT_Current_1], [PT_Resistance_1], [Result])
|
|
VALUES (@Serial, @PcbBarcode, @Qr, @Maker, @Model, @Var1, @Var2, @Operator, @ProdDate, @Line, @LotNo, @JigNo, @Vol1, @Cur1, @Res1, @Result);";
|
|
|
|
using (var conn = new System.Data.SqlClient.SqlConnection(_connectionString))
|
|
{
|
|
using (var cmd = new System.Data.SqlClient.SqlCommand(query, conn))
|
|
{
|
|
cmd.CommandTimeout = 5; // DB 응답 대기 최대 5초
|
|
cmd.Parameters.AddWithValue("@Serial", serial);
|
|
cmd.Parameters.AddWithValue("@PcbBarcode", safePcbBarcode);
|
|
cmd.Parameters.AddWithValue("@Qr", markingQr);
|
|
cmd.Parameters.AddWithValue("@Maker", safeMaker);
|
|
cmd.Parameters.AddWithValue("@Model", safeModel);
|
|
cmd.Parameters.AddWithValue("@Var1", safeVariant1);
|
|
cmd.Parameters.AddWithValue("@Var2", safeVariant2);
|
|
cmd.Parameters.AddWithValue("@Operator", safeOperator);
|
|
cmd.Parameters.AddWithValue("@ProdDate", prodDate);
|
|
cmd.Parameters.AddWithValue("@Line", safeLine);
|
|
cmd.Parameters.AddWithValue("@LotNo", safeLotNo);
|
|
cmd.Parameters.AddWithValue("@JigNo", safeJigNo);
|
|
|
|
cmd.Parameters.AddRange(measurementParameters);
|
|
cmd.Parameters.AddWithValue("@Result", safeResult);
|
|
|
|
conn.Open();
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
}
|
|
|
|
// 성공 로그 출력 및 true 반환
|
|
System.Diagnostics.Debug.WriteLine(string.Format(
|
|
"[DB 저장 로그] S/N: {0} | 각인번호: {1} | QR: {2} | Maker: {3} | Model: {4} | Line: {5} | LotNo: {6} | Vol_1: {7:F2}V | Cur_1: {8:F2}mA | Res_1: {9:F3}kΩ | 결과: {10} | 시작:{11} | 종료:{12}",
|
|
serial, markedSerial, markingQr, safeMaker, safeModel, safeLine, safeLotNo, roundedVoltage, roundedCurrent, roundedResistance, result,
|
|
startTime.ToString("HH:mm:ss"), endTime.ToString("HH:mm:ss")));
|
|
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine("[DB SaveResult] DB 저장 실패 (메시지: " + ex.Message + ")");
|
|
throw; // 예외 원인을 상위로 전파하여 UI 상태창에 구체적인 오류 원인(ex.Message)이 그대로 노출되도록 함
|
|
}
|
|
}
|
|
|
|
internal static System.Data.SqlClient.SqlParameter[] CreateMeasurementParameters(double voltage, double current, double resistance)
|
|
{
|
|
return new[]
|
|
{
|
|
new System.Data.SqlClient.SqlParameter("@Vol1", System.Data.SqlDbType.Decimal) { Precision = 5, Scale = 2, Value = Math.Round((decimal)voltage, 2) },
|
|
new System.Data.SqlClient.SqlParameter("@Cur1", System.Data.SqlDbType.Decimal) { Precision = 5, Scale = 2, Value = Math.Round((decimal)current, 2) },
|
|
new System.Data.SqlClient.SqlParameter("@Res1", System.Data.SqlDbType.Decimal) { Precision = 5, Scale = 2, Value = Math.Round((decimal)resistance, 2) }
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 지정한 LotNo로 기존에 저장된 제품들의 각인 순번 중 최대값을 구하여 다음 각인 일련번호(MAX + 1)를 구합니다.
|
|
/// 동일 시료 재검사 시 DB 행이 MERGE UPDATE 되어 레코드 수(COUNT)가 증가하지 않더라도 순번은 지속 증가합니다.
|
|
/// </summary>
|
|
public int GetNextMarkingSequence(string lotNo)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(lotNo)) return 1;
|
|
|
|
if (_offlineMode)
|
|
{
|
|
_offlineLotSequences.TryGetValue(lotNo.Trim(), out int maxSeq);
|
|
return maxSeq + 1;
|
|
}
|
|
|
|
try
|
|
{
|
|
// SQL Server 2022 호환: Marking_QR에서 맨 뒤 4자리를 TRY_CAST로 안전하게 정수로 변환하여 MAX 순번 산출 (NOLOCK 적용)
|
|
string query = @"
|
|
SELECT ISNULL(MAX(
|
|
TRY_CAST(
|
|
RIGHT(
|
|
RTRIM(
|
|
CASE
|
|
WHEN RTRIM([Marking_QR]) LIKE '%;' THEN LEFT(RTRIM([Marking_QR]), LEN(RTRIM([Marking_QR])) - 1)
|
|
ELSE RTRIM([Marking_QR])
|
|
END
|
|
), 4
|
|
) AS INT
|
|
)
|
|
), 0)
|
|
FROM [dbo].[Marking] WITH (NOLOCK)
|
|
WHERE [Lot_No] = @LotNo
|
|
AND [Marking_QR] IS NOT NULL
|
|
AND [Marking_QR] NOT LIKE N'NG:%'
|
|
AND LEN(RTRIM([Marking_QR])) >= 4;";
|
|
|
|
using (var conn = new System.Data.SqlClient.SqlConnection(_connectionString))
|
|
{
|
|
using (var cmd = new System.Data.SqlClient.SqlCommand(query, conn))
|
|
{
|
|
cmd.CommandTimeout = 5; // DB 응답 대기 최대 5초
|
|
cmd.Parameters.AddWithValue("@LotNo", lotNo.Trim());
|
|
conn.Open();
|
|
object resultObj = cmd.ExecuteScalar();
|
|
int maxSeq = (resultObj != null && resultObj != DBNull.Value) ? Convert.ToInt32(resultObj) : 0;
|
|
return maxSeq + 1;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
System.Diagnostics.Debug.WriteLine("[DB GetNextMarkingSequence] 에러 발생: " + ex.Message);
|
|
throw; // 예외 원인을 상위로 전파하여 UI 상태창에 구체적인 원인 출력
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|