chore: 首次簽入 Thinkyu ASP.NET 專案
- 加入 Visual Studio / ASP.NET .gitignore - 排除建置輸出、IDE 設定、NuGet packages、大型 MSI 安裝檔
This commit is contained in:
@@ -0,0 +1 @@
|
||||
<%@ WebHandler Language="C#" CodeBehind="ReceiptUploadHandler.ashx.cs" Class="Education.Services.ReceiptUploadHandler" %>
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using WebLib;
|
||||
|
||||
namespace Education.Services
|
||||
{
|
||||
public class ReceiptUploadHandler : IHttpHandler, System.Web.SessionState.IRequiresSessionState
|
||||
{
|
||||
public bool IsReusable => false;
|
||||
|
||||
public void ProcessRequest(HttpContext context)
|
||||
{
|
||||
context.Response.ContentType = "application/json; charset=utf-8";
|
||||
|
||||
try
|
||||
{
|
||||
if (context.Request.Files.Count == 0)
|
||||
{
|
||||
WriteResponse(context, false, "沒有檔案被上傳", null);
|
||||
return;
|
||||
}
|
||||
|
||||
HttpPostedFile uploadFile = context.Request.Files[0];
|
||||
if (uploadFile == null || uploadFile.ContentLength == 0)
|
||||
{
|
||||
WriteResponse(context, false, "檔案為空", null);
|
||||
return;
|
||||
}
|
||||
|
||||
string uploadDir = Path.Combine(context.Server.MapPath("~/"), "DocUpload");
|
||||
|
||||
if (!Directory.Exists(uploadDir))
|
||||
{
|
||||
Directory.CreateDirectory(uploadDir);
|
||||
}
|
||||
|
||||
// 保留原始副檔名,避免 NPOI LoadWorkbook 誤判格式
|
||||
string timestamp = PublicVariable.TaiwanNow.ToString("yyyyMMdd_HHmmss_fff");
|
||||
string ext = Path.GetExtension(uploadFile.FileName).ToLower();
|
||||
string tempFileName = Path.Combine(uploadDir, $"TrainFareImport_{timestamp}{ext}");
|
||||
|
||||
// 確保檔案是 XLSX 或 XLS 格式
|
||||
if (!uploadFile.FileName.EndsWith(".xlsx", StringComparison.OrdinalIgnoreCase) &&
|
||||
!uploadFile.FileName.EndsWith(".xls", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
WriteResponse(context, false, "只支援 .xlsx 或 .xls 格式的檔案", null);
|
||||
return;
|
||||
}
|
||||
|
||||
uploadFile.SaveAs(tempFileName);
|
||||
|
||||
context.Session["領據匯入檔案"] = tempFileName;
|
||||
|
||||
WriteResponse(context, true, "上傳成功", tempFileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteResponse(context, false, $"上傳失敗:{ex.Message}", null);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteResponse(HttpContext context, bool success, string message, string data)
|
||||
{
|
||||
var response = new
|
||||
{
|
||||
Success = success,
|
||||
Message = message,
|
||||
Data = data
|
||||
};
|
||||
context.Response.Write(JsonConvert.SerializeObject(response));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Web;
|
||||
using WebLib;
|
||||
|
||||
namespace Education.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// WebService 的基礎類別
|
||||
/// </summary>
|
||||
public class ServiceBase: System.Web.Services.WebService
|
||||
{
|
||||
public string CurrentUserId
|
||||
{
|
||||
get
|
||||
{
|
||||
if (HttpContext.Current != null && HttpContext.Current.Session != null)
|
||||
return DAC.GetString(HttpContext.Current.Session[PublicVariable.UserId]);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Xml.Serialization;
|
||||
using WebLib;
|
||||
|
||||
namespace Education.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 用來作爲服務回應的基底類別
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[SoapInclude(typeof(領據處理Dropdowns))]
|
||||
[SoapInclude(typeof(NameValueList))]
|
||||
[SoapInclude(typeof(NameValueItem))]
|
||||
public class ServiceResponse
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; }
|
||||
public object Data { get; set; }
|
||||
public int TotalCount { get; set; }
|
||||
public ServiceResponse()
|
||||
{
|
||||
Success = true;
|
||||
Message = string.Empty;
|
||||
Data = null;
|
||||
TotalCount = 0;
|
||||
}
|
||||
|
||||
public ServiceResponse(bool success, string message, object data = null, int totalCount = 0)
|
||||
{
|
||||
this.Success = success;
|
||||
this.Message = message;
|
||||
this.Data = data;
|
||||
this.TotalCount = totalCount;
|
||||
}
|
||||
|
||||
internal static ServiceResponse CreateSuccess(string message, object data = null, int totalCount = 0)
|
||||
{
|
||||
return new ServiceResponse(true, message, data, totalCount);
|
||||
}
|
||||
|
||||
internal static ServiceResponse CreateError(string message)
|
||||
{
|
||||
return new ServiceResponse(false, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<%@ WebHandler Language="C#" CodeBehind="TrainFareUploadHandler.ashx.cs" Class="Education.Services.TrainFareUploadHandler" %>
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using WebLib;
|
||||
|
||||
namespace Education.Services
|
||||
{
|
||||
public class TrainFareUploadHandler : IHttpHandler, System.Web.SessionState.IRequiresSessionState
|
||||
{
|
||||
public bool IsReusable => false;
|
||||
|
||||
public void ProcessRequest(HttpContext context)
|
||||
{
|
||||
context.Response.ContentType = "application/json; charset=utf-8";
|
||||
|
||||
try
|
||||
{
|
||||
if (context.Request.Files.Count == 0)
|
||||
{
|
||||
WriteResponse(context, false, "沒有檔案被上傳", null);
|
||||
return;
|
||||
}
|
||||
|
||||
HttpPostedFile uploadFile = context.Request.Files[0];
|
||||
if (uploadFile == null || uploadFile.ContentLength == 0)
|
||||
{
|
||||
WriteResponse(context, false, "檔案為空", null);
|
||||
return;
|
||||
}
|
||||
|
||||
string uploadDir = Path.Combine(context.Server.MapPath("~/"), "DocUpload");
|
||||
|
||||
if (!Directory.Exists(uploadDir))
|
||||
{
|
||||
Directory.CreateDirectory(uploadDir);
|
||||
}
|
||||
|
||||
// 使用 TrainFareImport_時間戳.xlsx 格式命名
|
||||
string timestamp = PublicVariable.TaiwanNow.ToString("yyyyMMdd_HHmmss_fff");
|
||||
string tempFileName = Path.Combine(uploadDir, $"TrainFareImport_{timestamp}.xlsx");
|
||||
|
||||
// 確保檔案是 XLSX 或 XLS 格式
|
||||
if (!uploadFile.FileName.EndsWith(".xlsx", StringComparison.OrdinalIgnoreCase) &&
|
||||
!uploadFile.FileName.EndsWith(".xls", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
WriteResponse(context, false, "只支援 .xlsx 或 .xls 格式的檔案", null);
|
||||
return;
|
||||
}
|
||||
|
||||
uploadFile.SaveAs(tempFileName);
|
||||
|
||||
context.Session["火車票價匯入檔案"] = tempFileName;
|
||||
|
||||
WriteResponse(context, true, "上傳成功", tempFileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteResponse(context, false, $"上傳失敗:{ex.Message}", null);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteResponse(HttpContext context, bool success, string message, string data)
|
||||
{
|
||||
var response = new
|
||||
{
|
||||
Success = success,
|
||||
Message = message,
|
||||
Data = data
|
||||
};
|
||||
context.Response.Write(JsonConvert.SerializeObject(response));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<system.web>
|
||||
<authorization>
|
||||
<allow users="*" />
|
||||
</authorization>
|
||||
</system.web>
|
||||
</configuration>
|
||||
@@ -0,0 +1 @@
|
||||
<%@ WebService Language="C#" CodeBehind="付款設定.asmx.cs" Class="Education.Services.付款設定" %>
|
||||
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Script.Services;
|
||||
using System.Web.Services;
|
||||
using WebLib;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Education.Services
|
||||
{
|
||||
/// <summary>
|
||||
///付款設定 的摘要描述
|
||||
/// </summary>
|
||||
[WebService(Namespace = "http://tempuri.org/")]
|
||||
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
|
||||
[System.ComponentModel.ToolboxItem(false)]
|
||||
[System.Web.Script.Services.ScriptService]
|
||||
public class 付款設定 : ServiceBase
|
||||
{
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json, UseHttpGet = false)]
|
||||
public ServiceResponse SelectPage(string 人員類別, string 講師編號, int PageIndex, int PageSize)
|
||||
{
|
||||
try
|
||||
{
|
||||
var startRowIndex = (PageIndex - 1) * PageSize;
|
||||
using (var conn = DAC.NewConnection())
|
||||
{
|
||||
var dao = new DAC_付款設定(conn);
|
||||
|
||||
// 根據講師編號查詢
|
||||
var list = dao.SelectPageBy講師_非講師編號(startRowIndex, PageSize, 講師編號);
|
||||
var count = dao.SelectCountBy講師_非講師編號(講師編號);
|
||||
|
||||
return new ServiceResponse()
|
||||
{
|
||||
Success = true,
|
||||
Data = list,
|
||||
TotalCount = count,
|
||||
Message = ""
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new ServiceResponse()
|
||||
{
|
||||
Success = false,
|
||||
Message = ex.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValid(付款設定Item item, out string message)
|
||||
{
|
||||
var messages = new List<string>();
|
||||
var valid = true;
|
||||
|
||||
if (item.生效日期.Ticks == 0)
|
||||
{
|
||||
messages.Add("請填寫生效日期");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(item.憑證類別))
|
||||
{
|
||||
messages.Add("請選擇憑證類別");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(item.扣繳類別))
|
||||
{
|
||||
messages.Add("請選擇扣繳類別");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
message = string.Join("<br/>", messages.ToArray());
|
||||
return valid;
|
||||
}
|
||||
|
||||
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json, UseHttpGet = false)]
|
||||
public ServiceResponse Save(string itemStr, bool isNew)
|
||||
{
|
||||
try
|
||||
{
|
||||
付款設定Item item = JsonConvert.DeserializeObject<付款設定Item>(itemStr);
|
||||
if (!IsValid(item, out string message))
|
||||
{
|
||||
return new ServiceResponse(false, message);
|
||||
}
|
||||
|
||||
using (var conn = DAC.NewConnection())
|
||||
{
|
||||
var dao = new DAC_付款設定(conn);
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
var result = dao.InsertOne(item);
|
||||
return new ServiceResponse();
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = dao.UpdateOne(item);
|
||||
return new ServiceResponse();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new ServiceResponse(false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json, UseHttpGet = false)]
|
||||
public ServiceResponse Delete(int 編號, int DB_APPNO)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var conn = DAC.NewConnection())
|
||||
{
|
||||
var dao = new DAC_付款設定(conn);
|
||||
var rowsAffected = dao.DeleteOne(編號, DB_APPNO);
|
||||
|
||||
if (rowsAffected > 0)
|
||||
return new ServiceResponse(true, "刪除成功");
|
||||
else
|
||||
return new ServiceResponse(false, "刪除失敗");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new ServiceResponse(false, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<%@ WebService Language="C#" CodeBehind="火車票價.asmx.cs" Class="Education.Services.火車票價" %>
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Services;
|
||||
using System.Web.Script.Services;
|
||||
using WebLib;
|
||||
|
||||
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 SelectOriginStation(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
DAC_火車票價 dac = new DAC_火車票價();
|
||||
var result = dac.查詢起站(key);
|
||||
|
||||
if (result.Count > 0)
|
||||
return ServiceResponse.CreateSuccess("查詢成功", result);
|
||||
return ServiceResponse.CreateError($"無符合資料");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ServiceResponse.CreateError($"查詢車站失敗:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public ServiceResponse SelectDestStation(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
DAC_火車票價 dac = new DAC_火車票價();
|
||||
var result = dac.查詢迄站(key);
|
||||
|
||||
if (result.Count > 0)
|
||||
return ServiceResponse.CreateSuccess("查詢成功", result);
|
||||
return ServiceResponse.CreateError($"無符合資料");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ServiceResponse.CreateError($"查詢車站失敗:{ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[WebMethod(EnableSession = true)]
|
||||
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
|
||||
public ServiceResponse SelectFare(string 起站, string 迄站)
|
||||
{
|
||||
try
|
||||
{
|
||||
DAC_火車票價 dac = new DAC_火車票價();
|
||||
var fare = dac.查詢票價(起站 != null ? 起站.Trim() : null, 迄站 != null ? 迄站.Trim() : null);
|
||||
|
||||
if (fare > 0)
|
||||
return ServiceResponse.CreateSuccess("查詢成功", fare);
|
||||
return ServiceResponse.CreateError($"無符合資料");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ServiceResponse.CreateError($"查詢票價失敗:{ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<%@ WebService Language="C#" CodeBehind="火車票價維護.asmx.cs" Class="Education.Services.火車票價維護" %>
|
||||
@@ -0,0 +1,782 @@
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<%@ WebService Language="C#" CodeBehind="領據處理.asmx.cs" Class="Education.Services.領據處理" %>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user