using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Script.Services; using System.Web.Services; using System.Data; using System.Data.Common; using System.IO; using WebLib; using Newtonsoft.Json; using NPOI.SS.UserModel; using NPOI.XSSF.UserModel; namespace Education.Services { [WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.ComponentModel.ToolboxItem(false)] [ScriptService] public class 火車票價維護 : ServiceBase { [WebMethod(EnableSession = true)] [ScriptMethod(ResponseFormat = ResponseFormat.Json)] public ServiceResponse GetVersions() { try { DAC_火車票價 dac = new DAC_火車票價(); var versions = dac.查詢版本號List(); return ServiceResponse.CreateSuccess("取得版本清單成功", versions); } catch (Exception ex) { return ServiceResponse.CreateError($"取得版本清單失敗:{ex.Message}"); } } [WebMethod(EnableSession = true)] [ScriptMethod(ResponseFormat = ResponseFormat.Json)] public ServiceResponse GetFareTable(int versionId) { try { DAC_火車票價 dac = new DAC_火車票價(); var data = dac.查詢票價表(versionId); if (data.Count == 0) { return ServiceResponse.CreateSuccess("無資料", new { originStations = new List(), destStations = new List(), fares = new Dictionary() }); } // 取得所有起站和訖站 var originStations = data.OrderBy(x => x.起站站碼).Select(x => x.起站).Distinct().ToList(); var destStations = data.OrderBy(x => x.迄站站碼).Select(x => x.迄站).Distinct().ToList(); // 起訖站由北至南排序 // 建立票價字典 var fares = new Dictionary(); foreach (var item in data) { var key = $"{item.起站}_{item.迄站}"; fares[key] = item.票價; } return ServiceResponse.CreateSuccess("取得票價表成功", new { originStations = originStations, destStations = destStations, fares = fares }); } catch (Exception ex) { return ServiceResponse.CreateError($"取得票價表失敗:{ex.Message}"); } } [WebMethod(EnableSession = true)] [ScriptMethod(ResponseFormat = ResponseFormat.Json)] public ServiceResponse SaveFareTable(int versionId, string fares) { try { Dictionary faresDict = JsonConvert.DeserializeObject>(fares); using (var ts = DAC.NewTransactionScope()) { using (var conn = DAC.NewConnection()) { DAC_火車票價 dac = new DAC_火車票價(conn); // 刪除原有的版本資料 var existingData = dac.Select(noInputReturnAll: true, 版本號: versionId, 起站: null, 迄站: null); // 保存原有的生效日期(應該所有同版本的記錄生效日期都一樣) DateTime effectiveDate = existingData.Count > 0 ? existingData[0].生效日期 : DateTime.Today; foreach (var item in existingData) { dac.DeleteOne(item.編號, item.DB_APPNO); } // 新增更新後的資料,使用保存的生效日期 foreach (var kvp in faresDict) { var parts = kvp.Key.Split('_'); if (parts.Length != 2) continue; var origin = parts[0]; var destination = parts[1]; if (origin == destination) continue; // 跳過同站 var newItem = new 火車票價Item { 版本號 = versionId, 生效日期 = effectiveDate, 起站 = origin, 迄站 = destination, 票價 = kvp.Value }; dac.InsertOne(newItem); } ts.Complete(); } } return ServiceResponse.CreateSuccess("儲存成功"); } catch (Exception ex) { return ServiceResponse.CreateError($"儲存失敗:{ex.Message}"); } } [WebMethod(EnableSession = true)] [ScriptMethod(ResponseFormat = ResponseFormat.Json)] public ServiceResponse CreateNewVersion(int versionId) { try { // 只保留年月日,不要時間 DateTime effectiveDate = PublicVariable.TaiwanNow.Date; using (var ts = DAC.NewTransactionScope()) { using (var conn = DAC.NewConnection()) { DAC_火車票價 dac = new DAC_火車票價(conn); // 檢查版本是否已存在 var existingData = dac.Select(noInputReturnAll: true, 版本號: versionId, 起站: null, 迄站: null); if (existingData.Count > 0) { return ServiceResponse.CreateError($"版本 {versionId} 已存在"); } // 建立預設車站清單(使用現有最新版本的車站) var latestVersion = dac.SelectAll().GroupBy(x => x.版本號).OrderByDescending(x => x.Key).FirstOrDefault(); if (latestVersion != null) { // 複製最新版本的結構(但票價為零) var stations = latestVersion.Select(x => x.起站).Distinct().ToList(); foreach (var origin in stations) { foreach (var dest in stations) { if (origin != dest) { var newItem = new 火車票價Item { 版本號 = versionId, 生效日期 = effectiveDate, 起站 = origin, 迄站 = dest, 票價 = 0 }; dac.InsertOne(newItem); } } } } ts.Complete(); } } return ServiceResponse.CreateSuccess("新版本建立成功"); } catch (Exception ex) { return ServiceResponse.CreateError($"建立新版本失敗:{ex.Message}"); } } [WebMethod(EnableSession = true)] [ScriptMethod(ResponseFormat = ResponseFormat.Json)] public ServiceResponse DeleteVersion(int versionId) { try { using (var ts = DAC.NewTransactionScope()) { using (var conn = DAC.NewConnection()) { DAC_火車票價 dac = new DAC_火車票價(conn); // 刪除該版本的所有資料 var existingData = dac.Select(noInputReturnAll: true, 版本號: versionId, 起站: null, 迄站: null); foreach (var item in existingData) { dac.DeleteOne(item.編號, item.DB_APPNO); } ts.Complete(); } } return ServiceResponse.CreateSuccess("版本刪除成功"); } catch (Exception ex) { return ServiceResponse.CreateError($"版本刪除失敗:{ex.Message}"); } } [WebMethod(EnableSession = true)] [ScriptMethod(ResponseFormat = ResponseFormat.Json)] public ServiceResponse ExportFareData(int versionId) { try { DAC_火車票價 dac = new DAC_火車票價(); var data = dac.Select(noInputReturnAll: true, 版本號: versionId, 起站: null, 迄站: null); List originStations; List destStations; var fares = new Dictionary(); if (data.Count == 0) { // 指定版本沒有資料,嘗試從其他版本取得車站清單 var allData = dac.SelectAll(); if (allData.Count == 0) { // 完全沒有任何資料,使用預設車站清單 originStations = GetDefaultOriginStations(); destStations = GetDefaultDestStations(); } else { // 使用現有版本的車站清單(會包含所有現有版本出現過的車站) originStations = allData.Select(x => x.起站).Distinct().ToList(); destStations = allData.Select(x => x.迄站).Distinct().ToList(); } } else { // 取得該版本的起站和訖站 originStations = data.Select(x => x.起站).Distinct().ToList(); destStations = data.Select(x => x.迄站).Distinct().ToList(); // 建立票價字典 foreach (var item in data) { var key = $"{item.起站}_{item.迄站}"; fares[key] = item.票價; } } // 建立 Excel 檔案 var excelData = CreateFareExcel(originStations, destStations, fares, versionId); var response = new { Data = new { originStations = originStations, destStations = destStations, fares = fares }, ExcelData = excelData, Success = true, Message = "匯出成功" }; return ServiceResponse.CreateSuccess("匯出成功", response); } catch (Exception ex) { return ServiceResponse.CreateError($"匯出失敗:{ex.Message}"); } } [WebMethod(EnableSession = true)] [ScriptMethod(ResponseFormat = ResponseFormat.Json)] public ServiceResponse GetExcelTemplate() { try { // 建立預設車站清單 var originStations = GetDefaultOriginStations(); var destStations = GetDefaultDestStations(); // 建立空的 Excel 範本 var excelData = CreateFareExcel(originStations, destStations, new Dictionary(), 0, isTemplate: true); return ServiceResponse.CreateSuccess("取得範本成功", excelData); } catch (Exception ex) { return ServiceResponse.CreateError($"取得範本失敗:{ex.Message}"); } } private List GetDefaultOriginStations() { return new List { "台北", "桃園", "中壢", "埔心", "楊梅", "竹北", "新竹", "竹南", "苗栗", "花蓮", "豐原", "清水", "台中" }; } private List GetDefaultDestStations() { return new List { "基隆", "八堵", "七堵", "汐止", "南港", "松山", "台北", "板橋", "樹林", "鶯歌", "桃園", "中壢", "楊梅", "竹北", "新竹", "竹東", "竹南", "苗栗", "豐原", "大甲", "清水", "台中", "沙鹿", "彰化", "員林", "田中", "二水", "斗六", "斗南", "嘉義", "新營", "善化", "台南", "岡山", "高雄", "屏東" }; } private string CreateFareExcel(List originStations, List destStations, Dictionary fares, int versionId, bool isTemplate = false) { XSSFWorkbook workbook = new XSSFWorkbook(); try { var sheet = workbook.CreateSheet("火車票價"); // 設定列寬 sheet.SetColumnWidth(0, 3500); for (int i = 1; i <= originStations.Count; i++) { sheet.SetColumnWidth(i, 2500); } // 設定標題列(橫軸為起站) var headerRow = sheet.CreateRow(0); headerRow.CreateCell(0).SetCellValue("訖站\\起站"); for (int i = 0; i < originStations.Count; i++) { headerRow.CreateCell(i + 1).SetCellValue(originStations[i]); } // 設定資料列(縱軸為訖站) for (int i = 0; i < destStations.Count; i++) { var row = sheet.CreateRow(i + 1); row.CreateCell(0).SetCellValue(destStations[i]); for (int j = 0; j < originStations.Count; j++) { var cell = row.CreateCell(j + 1); if (originStations[j] == destStations[i]) { // 同站,不填票價 cell.SetCellValue(""); } else { var key = $"{originStations[j]}_{destStations[i]}"; if (fares.ContainsKey(key)) { cell.SetCellValue(fares[key]); } else if (!isTemplate) { cell.SetCellValue(""); } } } } // 轉換為 Base64 using (var ms = new MemoryStream()) { workbook.Write(ms); return Convert.ToBase64String(ms.ToArray()); } } finally { workbook.Close(); } } [WebMethod(EnableSession = true)] [ScriptMethod(ResponseFormat = ResponseFormat.Json)] public ServiceResponse ImportFareData(string filePath) { try { // 驗證檔案路徑 if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) { return ServiceResponse.CreateError("檔案不存在或已過期"); } // 在迴圈外取得當前日期,只保留年月日 DateTime effectiveDate = PublicVariable.TaiwanNow.Date; // 從檔案讀取並驗證資料 var fares = new Dictionary(); var originStations = new List(); var destStations = new List(); using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { IWorkbook workbook = null; try { fileStream.Position = 0; workbook = new XSSFWorkbook(fileStream); } catch { DeleteUploadedFile(filePath); return ServiceResponse.CreateError("檔案格式錯誤"); } if (workbook == null || workbook.NumberOfSheets == 0) { DeleteUploadedFile(filePath); return ServiceResponse.CreateError("Excel 檔案格式錯誤"); } var sheet = workbook.GetSheetAt(0); if (sheet == null || sheet.PhysicalNumberOfRows == 0) { DeleteUploadedFile(filePath); return ServiceResponse.CreateError("Excel 工作表為空"); } // 讀取標題列 var headerRow = sheet.GetRow(0); if (headerRow == null) { DeleteUploadedFile(filePath); return ServiceResponse.CreateError("Excel 缺少標題列"); } // 取得起站列表(橫軸為起站,從第二欄開始) for (int i = 1; i < headerRow.PhysicalNumberOfCells; i++) { var cell = headerRow.GetCell(i); if (cell != null && !string.IsNullOrEmpty(cell.StringCellValue)) { originStations.Add(cell.StringCellValue); } } // 讀取資料列(縱軸為訖站,第一欄為訖站) for (int rowIdx = 1; rowIdx <= sheet.LastRowNum; rowIdx++) { var row = sheet.GetRow(rowIdx); if (row == null) continue; // 第一欄是訖站 var destCell = row.GetCell(0); if (destCell == null || string.IsNullOrEmpty(destCell.StringCellValue)) continue; string dest = destCell.StringCellValue; destStations.Add(dest); // 從第二欄開始是票價,對應起站(橫軸) for (int colIdx = 1; colIdx <= originStations.Count; colIdx++) { var fareCell = row.GetCell(colIdx); string origin = originStations[colIdx - 1]; if (origin == dest) continue; // 跳過同站 if (fareCell != null && fareCell.CellType != CellType.Blank) { try { int fare = (int)fareCell.NumericCellValue; if (fare >= 0) { var key = $"{origin}_{dest}"; fares[key] = fare; } } catch { // 忽略格式錯誤的單元格 } } } } } // 執行匯入 - 只有新增模式,自動產生新版本號 using (var ts = DAC.NewTransactionScope()) { using (var conn = DAC.NewConnection()) { DAC_火車票價 dac = new DAC_火車票價(conn); // 取得下一個版本號 int targetVersionId = 1; var allVersions = dac.SelectAll(); if (allVersions.Count > 0) { targetVersionId = allVersions.Max(x => x.版本號) + 1; } // 新增資料(使用迴圈外取得的 effectiveDate,只保留年月日) foreach (var kvp in fares) { var parts = kvp.Key.Split('_'); if (parts.Length != 2) continue; var origin = parts[0]; var destination = parts[1]; if (origin == destination) continue; // 跳過同站 var newItem = new 火車票價Item { 版本號 = targetVersionId, 生效日期 = effectiveDate, // 使用迴圈外的年月日 起站 = origin, 迄站 = destination, 票價 = kvp.Value }; dac.InsertOne(newItem); } ts.Complete(); } } // 匯入成功後刪除上傳的臨時檔案 DeleteUploadedFile(filePath); return ServiceResponse.CreateSuccess("匯入成功"); } catch (Exception ex) { // 匯入失敗也刪除上傳的檔案 if (!string.IsNullOrEmpty(filePath)) { DeleteUploadedFile(filePath); } return ServiceResponse.CreateError($"匯入失敗:{ex.Message}"); } } [WebMethod(EnableSession = true)] [ScriptMethod(ResponseFormat = ResponseFormat.Json)] public ServiceResponse ValidateImportFile(string filePath) { try { if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) { return ServiceResponse.CreateError("檔案不存在"); } var fares = new Dictionary(); var originStations = new List(); var destStations = new List(); var errors = new List(); int rowCount = 0; using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { IWorkbook workbook = null; // 嘗試讀取為 XLSX try { fileStream.Position = 0; workbook = new XSSFWorkbook(fileStream); } catch { // 檔案格式錯誤,刪除上傳的檔案 DeleteUploadedFile(filePath); return ServiceResponse.CreateError("只支援 .xlsx 格式的 Excel 檔案"); } if (workbook == null || workbook.NumberOfSheets == 0) { // 檔案格式錯誤,刪除上傳的檔案 DeleteUploadedFile(filePath); return ServiceResponse.CreateError("Excel 檔案格式錯誤"); } var sheet = workbook.GetSheetAt(0); if (sheet == null || sheet.PhysicalNumberOfRows == 0) { // 工作表為空,刪除上傳的檔案 DeleteUploadedFile(filePath); return ServiceResponse.CreateError("Excel 工作表為空"); } // 讀取標題列 var headerRow = sheet.GetRow(0); if (headerRow == null) { // 標題列缺失,刪除上傳的檔案 DeleteUploadedFile(filePath); return ServiceResponse.CreateError("Excel 缺少標題列"); } // 取得起站列表(橫軸為起站,從第二欄開始) for (int i = 1; i < headerRow.PhysicalNumberOfCells; i++) { var cell = headerRow.GetCell(i); if (cell != null && !string.IsNullOrEmpty(cell.StringCellValue)) { originStations.Add(cell.StringCellValue); } } // 讀取資料列(縱軸為訖站,第一欄為訖站) for (int rowIdx = 1; rowIdx <= sheet.LastRowNum; rowIdx++) { var row = sheet.GetRow(rowIdx); if (row == null) continue; // 第一欄是訖站 var destCell = row.GetCell(0); if (destCell == null || string.IsNullOrEmpty(destCell.StringCellValue)) continue; string dest = destCell.StringCellValue; destStations.Add(dest); // 從第二欄開始是票價,對應起站(橫軸) for (int colIdx = 1; colIdx <= originStations.Count; colIdx++) { var fareCell = row.GetCell(colIdx); string origin = originStations[colIdx - 1]; if (origin == dest) continue; // 跳過同站 if (fareCell != null && fareCell.CellType != CellType.Blank) { try { int fare = (int)fareCell.NumericCellValue; if (fare < 0) { errors.Add($"第 {rowIdx + 1} 列,{origin} 到 {dest} 的票價不能為負數"); } else { var key = $"{origin}_{dest}"; fares[key] = fare; } } catch { errors.Add($"第 {rowIdx + 1} 列,{origin} 到 {dest} 的票價格式不正確"); } } } rowCount++; } } if (errors.Count > 0) { // 驗證失敗,刪除上傳的檔案 DeleteUploadedFile(filePath); return ServiceResponse.CreateSuccess("檔案驗證失敗", new { Errors = errors, RowCount = 0 }); } return ServiceResponse.CreateSuccess("檔案驗證成功", new { Fares = fares, OriginStations = originStations.Distinct().ToList(), DestStations = destStations.Distinct().ToList(), Errors = new List() }); } catch (Exception ex) { // 驗證異常,刪除上傳的檔案 if (!string.IsNullOrEmpty(filePath)) { DeleteUploadedFile(filePath); } return ServiceResponse.CreateError($"驗證檔案失敗:{ex.Message}"); } } private void DeleteUploadedFile(string filePath) { if (string.IsNullOrEmpty(filePath)) { return; } try { // 刪除指定的檔案 if (File.Exists(filePath)) { File.Delete(filePath); } // 清理所有 TrainFareImport_*.xlsx 格式的檔案 string uploadDir = Path.Combine(HttpContext.Current.Server.MapPath("~/"), "DocUpload"); if (Directory.Exists(uploadDir)) { try { var searchPattern = "TrainFareImport_*.xlsx"; var oldFiles = Directory.GetFiles(uploadDir, searchPattern); foreach (var oldFile in oldFiles) { try { // 刪除30分鐘以前的檔案 var fileInfo = new FileInfo(oldFile); if (DateTime.Now - fileInfo.LastWriteTime > TimeSpan.FromMinutes(30)) { File.Delete(oldFile); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"清理舊檔案失敗:{oldFile} - {ex.Message}"); } } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"掃描清理檔案失敗:{ex.Message}"); } } } catch (Exception ex) { // 記錄刪除失敗但不拋出例外 System.Diagnostics.Debug.WriteLine($"刪除上傳檔案失敗:{filePath} - {ex.Message}"); } } } }