using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Web;
using System.Web.Script.Services;
using System.Web.Services;
using System.Web.Services.Description;
using WebLib;
namespace 報到系統
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[ScriptService]
public class SignUpService : System.Web.Services.WebService
{
///
/// 查詢參加人員
///
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public BaseResponse SearchSignUps(int pageIndex, int pageSize, int courseID, string name, string mobile)
{
try
{
// 計算起始索引 (pageIndex 從 1 開始)
int startRowIndex = (pageIndex - 1) * pageSize;
using (var conn = DAC.NewConnection())
{
DAC_SignUp dacSignUp = new DAC_SignUp(conn);
DAC_MessageLog dacMessageLog = new DAC_MessageLog(conn);
// 查詢資料
SignUpList result = dacSignUp.SelectPage(
noInputReturnAll: false,
startRowIndex: startRowIndex,
maximumRows: pageSize,
CourseID: courseID > 0 ? (int?)courseID : null,
SeqNo: null,
Name: name,
Mobile: mobile,
QRCode: null,
orderBy: "CourseID DESC, ID ASC"
);
// 取得總筆數
int totalCount = dacSignUp.SelectCount(
noInputReturnAll: false,
CourseID: courseID > 0 ? (int?)courseID : null,
SeqNo: null,
Name: name,
Mobile: mobile,
QRCode: null
);
// 查詢訊息發送紀錄
result.ForEach(p => {
var messageLogs = dacMessageLog.Select(false, p.CourseID, null, p.Name, p.Mobile);
bool hasSent = messageLogs.Find(q => q.Status == "SENT") != null;
p.MessageSending = messageLogs == null || messageLogs.Count == 0 ? "未發送" : (hasSent ? "發送成功" : "發送失敗");
});
return new BaseResponse
{
Success = true,
Message = "查詢成功",
Data = result ?? new SignUpList(),
TotalCount = totalCount
};
}
}
catch (Exception ex)
{
return new BaseResponse
{
Success = false,
Message = "查詢參加人員發生錯誤:" + ex.Message,
Data = new SignUpList(),
TotalCount = 0
};
}
}
///
/// 進階搜尋參加人員 - 支援序號、身分別、身分證字號、餐點、電子郵件等條件
///
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public BaseResponse SearchSignUpsAdvanced(int pageIndex, int pageSize, int courseID, int? seqNo, string name, string mobile,
string idType, string idNumber, bool idNumberEmpty, string mealType, string email, bool emailEmpty)
{
try
{
// 計算起始索引 (pageIndex 從 1 開始)
int startRowIndex = (pageIndex - 1) * pageSize;
using (var conn = DAC.NewConnection())
{
DAC_SignUp dacSignUp = new DAC_SignUp(conn);
DAC_MessageLog dacMessageLog = new DAC_MessageLog(conn);
// 查詢資料
SignUpList result = dacSignUp.SelectPageAdvanced(
noInputReturnAll: false,
startRowIndex: startRowIndex,
maximumRows: pageSize,
CourseID: courseID > 0 ? (int?)courseID : null,
SeqNo: seqNo,
Name: name,
Mobile: mobile,
IDType: idType,
IDNumber: idNumber,
IDNumberEmpty: idNumberEmpty,
MealType: mealType,
Email: email,
EmailEmpty: emailEmpty,
orderBy: "SeqNo ASC"
);
// 取得總筆數
int totalCount = dacSignUp.SelectCountAdvanced(
noInputReturnAll: false,
CourseID: courseID > 0 ? (int?)courseID : null,
SeqNo: seqNo,
Name: name,
Mobile: mobile,
IDType: idType,
IDNumber: idNumber,
IDNumberEmpty: idNumberEmpty,
MealType: mealType,
Email: email,
EmailEmpty: emailEmpty
);
// 查詢訊息發送紀錄
result.ForEach(p => {
var messageLogs = dacMessageLog.Select(false, p.CourseID, null, p.Name, p.Mobile);
bool hasSent = messageLogs.Find(q => q.Status == "SENT") != null;
p.MessageSending = messageLogs == null || messageLogs.Count == 0 ? "未發送" : (hasSent ? "發送成功" : "發送失敗");
});
return new BaseResponse
{
Success = true,
Message = "查詢成功",
Data = result ?? new SignUpList(),
TotalCount = totalCount
};
}
}
catch (Exception ex)
{
return new BaseResponse
{
Success = false,
Message = "進階查詢參加人員發生錯誤:" + ex.Message,
Data = new SignUpList(),
TotalCount = 0
};
}
}
///
/// 儲存參加人員 (新增或更新)
///
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public BaseResponse SaveSignUp(bool isNew, SignUpItem item)
{
try
{
if (item == null)
{
return new BaseResponse
{
Success = false,
Message = "參數不完整"
};
}
// 驗證必填欄位
if (item.CourseID <= 0)
{
return new BaseResponse
{
Success = false,
Message = "活動編號為必填欄位"
};
}
if (string.IsNullOrEmpty(item.Name?.Trim()))
{
return new BaseResponse
{
Success = false,
Message = "學員姓名為必填欄位"
};
}
if (item.Name.Length > 100)
{
return new BaseResponse
{
Success = false,
Message = "學員姓名最長 100 個字元"
};
}
if (string.IsNullOrEmpty(item.Mobile?.Trim()))
{
return new BaseResponse
{
Success = false,
Message = "手機號為必填欄位"
};
}
if (item.Mobile.Length > 20)
{
return new BaseResponse
{
Success = false,
Message = "手機號最長 20 個字元"
};
}
DAC_SignUp dac = new DAC_SignUp();
if (isNew)
{
// 新增 - 序號由 DAC_SignUp.BeforeInsertOne 自動生成
SignUpItem newItem = dac.InsertOne(item);
return new BaseResponse
{
Success = true,
Message = "新增成功",
Data = newItem
};
}
else
{
// 更新 - 保護現有的關鍵欄位(QRCode、SeqNo)
// 如果前端沒有傳遞這些欄位,則保留原有值
SignUpList existing = dac.SelectOne(item.ID);
if (existing != null && existing.Count > 0)
{
SignUpItem existingItem = existing[0];
// 如果新數據中的 QRCode 為空,保留原有值
if (string.IsNullOrEmpty(item.QRCode))
{
item.QRCode = existingItem.QRCode;
}
// 如果新數據中的 SeqNo 為 0,保留原有值
if (item.SeqNo <= 0)
{
item.SeqNo = existingItem.SeqNo;
}
}
SignUpItem updatedItem = dac.UpdateOne(item);
return new BaseResponse
{
Success = true,
Message = "更新成功",
Data = updatedItem
};
}
}
catch (Exception ex)
{
return new BaseResponse
{
Success = false,
Message = "儲存參加人員發生錯誤:" + ex.Message
};
}
}
///
/// 刪除參加人員
///
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public BaseResponse DeleteSignUp(int id, int dbAppNo)
{
try
{
if (id <= 0)
{
return new BaseResponse
{
Success = false,
Message = "參數不完整"
};
}
DAC_SignUp dac = new DAC_SignUp();
int rowsAffected = dac.DeleteOne(id, dbAppNo);
if (rowsAffected > 0)
{
return new BaseResponse
{
Success = true,
Message = "刪除成功"
};
}
else
{
return new BaseResponse
{
Success = false,
Message = "找不到要刪除的資料或資料已被修改"
};
}
}
catch (Exception ex)
{
return new BaseResponse
{
Success = false,
Message = "刪除參加人員發生錯誤:" + ex.Message
};
}
}
///
/// 確認匯入 - 將驗證後的資料入庫
/// 若姓名+手機號重複則跳過
///
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public BaseResponse ConfirmImport(int courseID, List items, bool clearBefore = false)
{
try
{
if (courseID <= 0)
{
return new BaseResponse
{
Success = false,
Message = "活動編號為必填欄位"
};
}
if (items == null || items.Count == 0)
{
return new BaseResponse
{
Success = false,
Message = "沒有要匯入的資料"
};
}
using (var ts = DAC.NewTransactionScope())
{
DAC_SignUp dac = new DAC_SignUp();
// 如果選擇「先清除再匯入」,則刪除該活動的所有現有記錄
if (clearBefore)
{
dac.DeleteByCourseID(courseID);
}
// 查詢現有的參加人員(同一活動)
SignUpList existingSignups = dac.Select(
noInputReturnAll: true,
CourseID: courseID,
SeqNo: null,
Name: null,
Mobile: null,
QRCode: null,
orderBy: "ID ASC"
);
// 建立一個集合存儲現有的 (姓名, 手機號) 組合,用於快速查詢
HashSet existingCombinations = new HashSet();
if (existingSignups != null && existingSignups.Count > 0)
{
foreach (var signup in existingSignups)
{
string key = CreateCompositeKey(signup.Name, signup.Mobile);
existingCombinations.Add(key);
}
}
int successCount = 0;
int skippedCount = 0;
List importErrors = new List();
List skippedRecords = new List();
foreach (var item in items)
{
try
{
// 檢查是否重複
string compositeKey = CreateCompositeKey(item.Name, item.Mobile);
if (existingCombinations.Contains(compositeKey))
{
skippedCount++;
skippedRecords.Add($"{item.Name} ({item.Mobile})");
continue;
}
// 不重複,執行插入
// 序號由 DAC_SignUp.BeforeInsertOne 自動生成,清除任何手動設定的值
item.CourseID = courseID;
item.SeqNo = 0; // 重置序號,讓 BeforeInsertOne 自動生成
dac.InsertOne(item);
successCount++;
// 將新插入的記錄加入集合,避免同一批次中的重複
existingCombinations.Add(compositeKey);
}
catch (Exception ex)
{
importErrors.Add($"匯入失敗:{item.Name} ({item.Mobile}) - {ex.Message}");
}
}
ts.Complete();
string message = $"成功匯入 {successCount} 筆資料";
if (skippedCount > 0)
{
message += $",跳過 {skippedCount} 筆重複資料";
}
if (importErrors.Count > 0)
{
message += $",失敗 {importErrors.Count} 筆";
}
return new BaseResponse
{
Success = true,
Message = message,
Data = new
{
SuccessCount = successCount,
SkippedCount = skippedCount,
ErrorCount = importErrors.Count,
Errors = importErrors,
SkippedRecords = skippedRecords
}
};
}
}
catch (Exception ex)
{
return new BaseResponse
{
Success = false,
Message = "確認匯入發生錯誤:" + ex.Message
};
}
}
///
/// 建立複合鍵 (姓名+手機號),用於檢查重複
///
private string CreateCompositeKey(string name, string mobile)
{
return (name ?? "").Trim() + "|" + (mobile ?? "").Trim();
}
///
/// 匯出參加人員 - 使用 NPOI 生成 Excel 檔案並回傳 Base64
///
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public BaseResponse ExportSignUps(int courseID, string timeFormat = "datetime")
{
try
{
if (courseID <= 0)
{
return new BaseResponse
{
Success = false,
Message = "活動編號為必填欄位"
};
}
DAC_SignUp dac = new DAC_SignUp();
DAC_Classes dacClasses = new DAC_Classes();
// 查詢參加人員
SignUpList signups = dac.Select(
noInputReturnAll: true,
CourseID: courseID,
SeqNo: null,
Name: null,
Mobile: null,
QRCode: null,
orderBy: "ID ASC"
);
// 查詢活動資訊
ClassesList classes = dacClasses.SelectOne(courseID);
if (classes.Count == 0)
{
return new BaseResponse
{
Success = false,
Message = "找不到活動資訊"
};
}
ClassesItem classItem = classes[0];
// 使用 NPOI 建立 Excel 檔案
IWorkbook workbook = new XSSFWorkbook();
ISheet sheet = workbook.CreateSheet("參加人員");
// 建立字體和樣式
IFont headerFont = workbook.CreateFont();
headerFont.IsBold = true;
headerFont.FontHeightInPoints = 11;
ICellStyle headerStyle = workbook.CreateCellStyle();
headerStyle.SetFont(headerFont);
headerStyle.Alignment = HorizontalAlignment.Center;
headerStyle.VerticalAlignment = VerticalAlignment.Center;
ICellStyle dataStyle = workbook.CreateCellStyle();
dataStyle.Alignment = HorizontalAlignment.Left;
dataStyle.VerticalAlignment = VerticalAlignment.Center;
int rowIndex = 0;
// 活動資訊
IRow infoRow1 = sheet.CreateRow(rowIndex++);
infoRow1.CreateCell(0).SetCellValue("活動名稱");
infoRow1.CreateCell(1).SetCellValue(classItem.CourseName);
IRow infoRow2 = sheet.CreateRow(rowIndex++);
infoRow2.CreateCell(0).SetCellValue("活動日期");
infoRow2.CreateCell(1).SetCellValue(classItem.CourseDate.ToString("yyyy-MM-dd"));
IRow infoRow3 = sheet.CreateRow(rowIndex++);
infoRow3.CreateCell(0).SetCellValue("活動地點");
infoRow3.CreateCell(1).SetCellValue(classItem.CourseLocation);
// 空行
rowIndex++;
// 表頭
IRow headerRow = sheet.CreateRow(rowIndex++);
headerRow.CreateCell(0).SetCellValue("序號");
headerRow.GetCell(0).CellStyle = headerStyle;
headerRow.CreateCell(1).SetCellValue("學員姓名");
headerRow.GetCell(1).CellStyle = headerStyle;
headerRow.CreateCell(2).SetCellValue("手機號");
headerRow.GetCell(2).CellStyle = headerStyle;
headerRow.CreateCell(3).SetCellValue("簽到時間");
headerRow.GetCell(3).CellStyle = headerStyle;
headerRow.CreateCell(4).SetCellValue("簽退時間");
headerRow.GetCell(4).CellStyle = headerStyle;
headerRow.CreateCell(5).SetCellValue("身分別");
headerRow.GetCell(5).CellStyle = headerStyle;
headerRow.CreateCell(6).SetCellValue("身分證字號");
headerRow.GetCell(6).CellStyle = headerStyle;
headerRow.CreateCell(7).SetCellValue("餐點");
headerRow.GetCell(7).CellStyle = headerStyle;
headerRow.CreateCell(8).SetCellValue("電子郵件");
headerRow.GetCell(8).CellStyle = headerStyle;
headerRow.CreateCell(9).SetCellValue("失業給付認定次數");
headerRow.GetCell(9).CellStyle = headerStyle;
headerRow.CreateCell(10).SetCellValue("性別");
headerRow.GetCell(10).CellStyle = headerStyle;
headerRow.CreateCell(11).SetCellValue("報名方式");
headerRow.GetCell(11).CellStyle = headerStyle;
// 新增儲存格註解
IDrawing drawing = sheet.CreateDrawingPatriarch();
IClientAnchor anchor5 = workbook.GetCreationHelper().CreateClientAnchor();
anchor5.Col1 = 5;
anchor5.Row1 = headerRow.RowNum;
IComment comment5 = drawing.CreateCellComment(anchor5);
comment5.String = new XSSFRichTextString("失業給付 / 一般民眾 / 青年尋職");
headerRow.GetCell(5).CellComment = comment5;
IClientAnchor anchor7 = workbook.GetCreationHelper().CreateClientAnchor();
anchor7.Col1 = 7;
anchor7.Row1 = headerRow.RowNum;
IComment comment7 = drawing.CreateCellComment(anchor7);
comment7.String = new XSSFRichTextString("素食 / 非素食 / 不用餐");
headerRow.GetCell(7).CellComment = comment7;
// 資料行
foreach (var signup in signups)
{
IRow dataRow = sheet.CreateRow(rowIndex++);
dataRow.CreateCell(0).SetCellValue(signup.SeqNo);
dataRow.CreateCell(1).SetCellValue(signup.Name);
dataRow.CreateCell(2).SetCellValue(signup.Mobile);
dataRow.CreateCell(3).SetCellValue(Utils.FormatTimeValue(signup.CheckInTime, timeFormat));
dataRow.CreateCell(4).SetCellValue(Utils.FormatTimeValue(signup.CheckOutTime, timeFormat));
dataRow.CreateCell(5).SetCellValue(signup.IDType ?? "");
dataRow.CreateCell(6).SetCellValue(signup.IDNumber ?? "");
dataRow.CreateCell(7).SetCellValue(signup.MealType ?? "");
dataRow.CreateCell(8).SetCellValue(signup.Email ?? "");
dataRow.CreateCell(9).SetCellValue(signup.LossJobTimes.HasValue ? signup.LossJobTimes.Value.ToString() : "");
dataRow.CreateCell(10).SetCellValue(signup.Gender ?? "");
dataRow.CreateCell(11).SetCellValue(signup.SignUpType ?? "未設定");
for (int i = 0; i < 10; i++)
{
dataRow.GetCell(i).CellStyle = dataStyle;
}
}
// 設定欄寬
sheet.SetColumnWidth(0, 10 * 256);
sheet.SetColumnWidth(1, 15 * 256);
sheet.SetColumnWidth(2, 15 * 256);
sheet.SetColumnWidth(3, (timeFormat == "datetime" ? 20 : 10) * 256);
sheet.SetColumnWidth(4, (timeFormat == "datetime" ? 20 : 10) * 256);
sheet.SetColumnWidth(5, 12 * 256);
sheet.SetColumnWidth(6, 15 * 256);
sheet.SetColumnWidth(7, 12 * 256);
sheet.SetColumnWidth(8, 20 * 256);
sheet.SetColumnWidth(9, 20 * 256);
sheet.SetColumnWidth(10, 20 * 256);
sheet.SetColumnWidth(11, 12 * 256);
// 儲存到記憶體流並轉換為 Base64
MemoryStream ms = new MemoryStream();
workbook.Write(ms);
// ms.Position = 0;
byte[] fileBytes = ms.ToArray();
string fileBase64 = Convert.ToBase64String(fileBytes);
string fileName = $"{classItem.CourseName}_{DateTime.Now:yyyyMMdd}.xlsx";
// ms.Close();
// workbook.Close();
return new BaseResponse
{
Success = true,
Message = "匯出成功",
Data = new
{
FileName = fileName,
FileBase64 = fileBase64,
FileSize = fileBytes.Length,
DataCount = signups.Count
}
};
}
catch (Exception ex)
{
return new BaseResponse
{
Success = false,
Message = "匯出參加人員發生錯誤:" + ex.Message
};
}
}
///
/// 取得單元格的值(文字)
///
private string GetCellValue(ICell cell)
{
if (cell == null) return "";
switch (cell.CellType)
{
case CellType.String:
return cell.StringCellValue;
case CellType.Numeric:
return cell.NumericCellValue.ToString();
case CellType.Boolean:
return cell.BooleanCellValue.ToString();
default:
return "";
}
}
///
/// 判斷是否為表頭行
///
private bool IsHeaderRow(string col1, string col2)
{
string[] headerKeywords = { "姓名", "名字", "name", "Name", "手機", "電話", "mobile", "phone" };
foreach (var keyword in headerKeywords)
{
if ((col1 ?? "").ToLower().Contains(keyword.ToLower()) ||
(col2 ?? "").ToLower().Contains(keyword.ToLower()))
{
return true;
}
}
return false;
}
///
/// 轉換日期時間字串 yyyy-MM-dd HH:mm:ss 為日期
///
private DateTime? TransferDateTimeString(string dateTime)
{
if (string.IsNullOrEmpty(dateTime))
return null;
string format = "yyyy-MM-dd HH:mm:ss";
DateTime result;
if (DateTime.TryParseExact(
dateTime.Trim(),
format,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out result))
return result;
return null;
}
///
/// 批發簡訊 - 向選定的人員批量發送簡訊
///
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public BaseResponse SendSmsBatch(int courseID, List signupIds)
{
try
{
if (courseID <= 0)
{
return new BaseResponse
{
Success = false,
Message = "活動編號為必填欄位"
};
}
if (signupIds == null || signupIds.Count == 0)
{
return new BaseResponse
{
Success = false,
Message = "請先選擇要發送的人員"
};
}
int successCount = 0;
int failedCount = 0;
List errors = new List();
List successRecords = new List();
// 建立 ZXing writer 實例
var writer = new ZXing.BarcodeWriter
{
Format = ZXing.BarcodeFormat.QR_CODE,
Options = new ZXing.QrCode.QrCodeEncodingOptions
{
ErrorCorrection = ZXing.QrCode.Internal.ErrorCorrectionLevel.M,
Width = 150,
Height = 150,
Margin = 1,
CharacterSet = "UTF-8"
}
};
using (var conn = DAC.NewConnection())
{
DAC_Classes dacClasses = new DAC_Classes(conn);
DAC_SignUp dacSignUp = new DAC_SignUp(conn);
DAC_MessageQueue dacQueue = new DAC_MessageQueue(conn);
var classItem = dacClasses.SelectOne(courseID).FirstOrDefault();
if (classItem == null)
return new BaseResponse
{
Success = false,
Message = "無此課程編號"
};
if (string.IsNullOrEmpty(classItem.NotificationTitle))
return new BaseResponse
{
Success = false,
Message = "尚未設定此課程的通知標題"
};
if (string.IsNullOrEmpty(classItem.NotificationTemplate))
return new BaseResponse
{
Success = false,
Message = "尚未設定此課程的通知模版"
};
// 逐筆查詢要發送的參加人員
foreach (var signupId in signupIds)
{
try
{
// 查詢該人員的資訊
SignUpList signupList = dacSignUp.SelectOne(signupId);
if (signupList == null || signupList.Count == 0)
{
errors.Add($"無法找到編號 {signupId} 的參加人員資料");
failedCount++;
continue;
}
SignUpItem signupItem = signupList[0];
// 檢查手機號是否存在
if (string.IsNullOrEmpty(signupItem.Mobile?.Trim()))
{
errors.Add($"參加人員「{signupItem.Name}」缺少手機號");
failedCount++;
continue;
}
// 檢查是否有 QRCode 資料
string qrCodeBase64 = null;
if (string.IsNullOrEmpty(signupItem.QRCode))
{
signupItem.QRCode = DAC_SignUp.GenQRCodeText(signupItem);
dacSignUp.UpdateOne(signupItem);
}
// 產生 QRCode 圖檔並轉成 BASE64
try
{
using (var bitmap = writer.Write(signupItem.QRCode))
using (var ms = new MemoryStream())
{
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
qrCodeBase64 = Convert.ToBase64String(ms.ToArray());
}
}
catch (Exception ex)
{
errors.Add($"參加人員「{signupItem.Name}」產生 QRCode 失敗:{ex.Message}");
failedCount++;
continue;
}
// 寫入 MessageQueue
dacQueue.InsertOne(new MessageQueueItem()
{
CourseID = signupItem.CourseID,
Status = "PENDING",
Name = signupItem.Name,
Mobile = signupItem.Mobile,
Subject = classItem.NotificationTitle,
Content = classItem.NotificationTemplate
.Replace("{課程日期}", classItem.CourseDate.ToString("yyyy-MM-dd HH:mm"))
.Replace("{課程名稱}", classItem.CourseName)
.Replace("{課程地點}", classItem.CourseLocation)
.Replace("{活動日期}", classItem.CourseDate.ToString("yyyy-MM-dd HH:mm"))
.Replace("{活動名稱}", classItem.CourseName)
.Replace("{活動地點}", classItem.CourseLocation)
.Replace("{學員姓名}", signupItem.Name)
.Replace("{開始時間}", classItem.StartTime)
.Replace("{結束時間}", classItem.EndTime)
.Replace("{講師}", classItem.Teacher)
,
QRCode = qrCodeBase64
});
successCount++;
successRecords.Add($"{signupItem.Name} ({signupItem.Mobile})");
Console.WriteLine($"[SMS] 預約發送簡訊給 {signupItem.Name} ({signupItem.Mobile})");
}
catch (Exception ex)
{
errors.Add($"預約發送給編號 {signupId} 的人員失敗:{ex.Message}");
failedCount++;
}
}
}
string message = $"成功預約發送 {successCount} 條簡訊";
if (failedCount > 0)
{
message += $",失敗 {failedCount} 條";
}
// 測試用,啟動發送簡訊服務
// SMSSendingService.Instance.Start();
return new BaseResponse
{
Success = successCount > 0,
Message = message,
Data = new
{
SuccessCount = successCount,
FailedCount = failedCount,
SuccessRecords = successRecords,
Errors = errors,
TotalRequested = signupIds.Count
}
};
}
catch (Exception ex)
{
return new BaseResponse
{
Success = false,
Message = "批發簡訊發生錯誤:" + ex.Message
};
}
}
///
/// 查詢簡訊點數餘額
///
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public BaseResponse CheckBalance()
{
switch (PublicVariable.CurrentMMSPlatform)
{
case "EVERY8D":
var success = EVERY8DSMSProvider.CheckBalance(out int balance, out string errorMessage);
if (success)
{
return new BaseResponse { Success = true, Data = new { Balance = balance } };
}
else
{
return new BaseResponse { Success = false, Message = errorMessage };
}
case "MITAKE":
default:
return new BaseResponse { Success = false, Message = "三竹簡訊餘額請至三竹網站查詢" };
}
}
///
/// 查詢單筆參加人員
///
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public BaseResponse GetSignUpById(int signupId)
{
try
{
if (signupId <= 0)
{
return new BaseResponse
{
Success = false,
Message = "參加人員編號為必填欄位"
};
}
DAC_SignUp dac = new DAC_SignUp();
SignUpList result = dac.SelectOne(signupId);
if (result == null || result.Count == 0)
{
return new BaseResponse
{
Success = false,
Message = "找不到該參加人員"
};
}
return new BaseResponse
{
Success = true,
Message = "查詢成功",
Data = result[0]
};
}
catch (Exception ex)
{
return new BaseResponse
{
Success = false,
Message = "查詢參加人員發生錯誤:" + ex.Message
};
}
}
///
/// 批量上傳簽到簽退資料 - 由 WinForm 客戶端上傳簽到簽退資料
/// 驗證規則:
/// - CourseID 必須存在於課程表
/// - Name、Mobile、SignUpType 必填
/// - SignUpType 允許值:預約 / 現場
/// - CheckInType 允許值:CHECKIN / CHECKOUT
/// - CheckInTime 驗證格式:yyyy-MM-dd HH:mm:ss
/// 處理邏輯:
/// - 用 CourseID 查詢 Classes.ID,若不存在則放棄這筆,記入錯誤筆數
/// - 按 (CourseID, Name, Mobile) 查詢現有 SignUp 記錄
/// - 若找到則依照 CheckInType 更新 CheckInTime/CheckOutTime,計入成功筆數
/// - 若沒找到則新增 (SignUpType="現場"),計入成功筆數
///
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public BaseResponse UploadCheckIns(int courseID, List items)
{
try
{
if (courseID <= 0)
{
return new BaseResponse
{
Success = false,
Message = "活動編號為必填欄位"
};
}
if (items == null || items.Count == 0)
{
return new BaseResponse
{
Success = false,
Message = "沒有要同步的資料"
};
}
// 驗證 CourseID 是否存在
DAC_Classes dacClasses = new DAC_Classes();
ClassesList courseList = dacClasses.SelectOne(courseID);
if (courseList == null || courseList.Count == 0)
{
return new BaseResponse
{
Success = false,
Message = $"課程編號 {courseID} 不存在"
};
}
DAC_SignUp dac = new DAC_SignUp();
int createdCount = 0;
int updatedCount = 0;
int failedCount = 0;
int skippedCount = 0;
List errors = new List();
List successIDs = new List();
for (int i = 0; i < items.Count; i++)
{
var item = items[i];
int itemIndex = i + 1;
try
{
// 驗證必填欄位
if (string.IsNullOrEmpty(item.Name?.Trim()))
{
errors.Add($"第 {itemIndex} 筆:姓名為必填欄位");
failedCount++;
continue;
}
if (string.IsNullOrEmpty(item.Mobile?.Trim()))
{
errors.Add($"第 {itemIndex} 筆:手機號為必填欄位");
failedCount++;
continue;
}
if (string.IsNullOrEmpty(item.SignUpType?.Trim()))
{
errors.Add($"第 {itemIndex} 筆:報名方式為必填欄位");
failedCount++;
continue;
}
// 驗證 SignUpType 允許值
string signUpType = item.SignUpType.Trim();
// 沒有值代表現場
if (string.IsNullOrEmpty(signUpType)) signUpType = "現場";
if (signUpType != "預約" && signUpType != "現場")
{
errors.Add($"第 {itemIndex} 筆:報名方式只允許『預約』或『現場』,目前值為『{signUpType}』");
failedCount++;
continue;
}
// 驗證 CheckInType
if (string.IsNullOrEmpty(item.CheckInType?.Trim()))
{
errors.Add($"第 {itemIndex} 筆:簽到類別為必填欄位");
failedCount++;
continue;
}
string checkInType = item.CheckInType.Trim();
if (checkInType != "CHECKIN" && checkInType != "CHECKOUT")
{
errors.Add($"第 {itemIndex} 筆:簽到類別只允許『CHECKIN』或『CHECKOUT』,目前值為『{checkInType}』");
failedCount++;
continue;
}
// 驗證 CheckInTime
if (string.IsNullOrEmpty(item.CheckInTime))
{
errors.Add($"第 {itemIndex} 筆:簽到/簽退時間為必填欄位");
failedCount++;
continue;
}
// 驗證 CheckInTime 格式(UTC 時間已經是有效格式)
var checkinTime = TransferDateTimeString(item.CheckInTime);
if (!checkinTime.HasValue)
{
errors.Add($"第 {itemIndex} 筆:簽到時間格式不正確,應為 yyyy-MM-dd HH:mm:ss");
failedCount++;
continue;
}
// 按 (CourseID, Name, Mobile) 查詢現有記錄
SignUpList existing = dac.SelectByCourseNameMobile(courseID, item.Name, item.Mobile);
if (existing != null && existing.Count > 0)
{
// 記錄存在,依照 CheckInType 更新相應的時間欄位
SignUpItem existingItem = existing[0];
bool needUpdate = false;
if (checkInType == "CHECKIN")
{
existingItem.CheckInTime = item.CheckInTime;
existingItem.AgreementStatus = item.AgreementStatus;
needUpdate = true;
}
else if (checkInType == "CHECKOUT")
{
existingItem.CheckOutTime = item.CheckInTime;
needUpdate = true;
}
if (needUpdate)
{
dac.UpdateOne(existingItem);
updatedCount++;
successIDs.Add(item.ID.ToString());
}
else
{
skippedCount++;
}
}
else
{
// 記錄不存在,新增為現場簽到
// 序號由 DAC_SignUp.BeforeInsertOne 自動生成
SignUpItem newItem = new SignUpItem
{
CourseID = courseID,
Name = item.Name,
Mobile = item.Mobile,
IDNumber = item.IDNumber,
IDType = item.IDType,
MealType = item.MealType,
Email = item.Email,
SignUpType = "現場",
QRCode = null,
};
// 根據 CheckInType 設定簽到/簽退時間
if (checkInType == "CHECKIN")
{
newItem.CheckInTime = item.CheckInTime;
}
else if (checkInType == "CHECKOUT")
{
newItem.CheckOutTime = item.CheckInTime;
}
dac.InsertOne(newItem);
createdCount++;
successIDs.Add(item.ID.ToString());
}
}
catch (Exception ex)
{
errors.Add($"第 {itemIndex} 筆:{item.Name} ({item.Mobile}) - {ex.Message}");
failedCount++;
}
}
string message = $"成功新增 {createdCount} 筆,更新 {updatedCount} 筆";
if (failedCount > 0)
{
message += $",失敗 {failedCount} 筆";
}
if (skippedCount > 0)
{
message += $",跳過 {skippedCount} 筆";
}
return new BaseResponse
{
Success = createdCount > 0 || updatedCount > 0,
Message = message,
Data = new
{
CreatedCount = createdCount,
UpdatedCount = updatedCount,
FailedCount = failedCount,
SkippedCount = skippedCount,
SuccessIDs = string.Join(",", successIDs.ToArray()),
Errors = errors
}
};
}
catch (Exception ex)
{
return new BaseResponse
{
Success = false,
Message = "批量上傳簽到簽退資料發生錯誤:" + ex.Message
};
}
}
}
}