Files
thinkyu/branches/1150807/Services/火車票價維護.asmx.cs
T
sryang 577060bc78 chore: 首次簽入 Thinkyu ASP.NET 專案
- 加入 Visual Studio / ASP.NET .gitignore
- 排除建置輸出、IDE 設定、NuGet packages、大型 MSI 安裝檔
2026-09-10 09:42:37 +08:00

783 lines
31 KiB
C#

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<string>(),
destStations = new List<string>(),
fares = new Dictionary<string, int>()
});
}
// 取得所有起站和訖站
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<string, int>();
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<string, int> faresDict = JsonConvert.DeserializeObject<Dictionary<string, int>>(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<string> originStations;
List<string> destStations;
var fares = new Dictionary<string, int>();
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<string, int>(), 0, isTemplate: true);
return ServiceResponse.CreateSuccess("取得範本成功", excelData);
}
catch (Exception ex)
{
return ServiceResponse.CreateError($"取得範本失敗:{ex.Message}");
}
}
private List<string> GetDefaultOriginStations()
{
return new List<string>
{
"台北", "桃園", "中壢", "埔心", "楊梅", "竹北", "新竹", "竹南", "苗栗",
"花蓮", "豐原", "清水", "台中"
};
}
private List<string> GetDefaultDestStations()
{
return new List<string>
{
"基隆", "八堵", "七堵", "汐止", "南港", "松山", "台北", "板橋", "樹林",
"鶯歌", "桃園", "中壢", "楊梅", "竹北", "新竹", "竹東", "竹南", "苗栗",
"豐原", "大甲", "清水", "台中", "沙鹿", "彰化", "員林", "田中", "二水",
"斗六", "斗南", "嘉義", "新營", "善化", "台南", "岡山", "高雄", "屏東"
};
}
private string CreateFareExcel(List<string> originStations, List<string> destStations,
Dictionary<string, int> 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<string, int>();
var originStations = new List<string>();
var destStations = new List<string>();
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<string, int>();
var originStations = new List<string>();
var destStations = new List<string>();
var errors = new List<string>();
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<string>()
});
}
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}");
}
}
}
}