chore: 首次簽入 Thinkyu ASP.NET 專案
- 加入 Visual Studio / ASP.NET .gitignore - 排除建置輸出、IDE 設定、NuGet packages、大型 MSI 安裝檔
This commit is contained in:
@@ -0,0 +1,463 @@
|
||||
using NPOI.HSSF.UserModel;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using NPOI.SS.UserModel;
|
||||
using NPOI.XSSF.UserModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using WebLib;
|
||||
|
||||
namespace Education
|
||||
{
|
||||
public class ImportBase
|
||||
{
|
||||
protected WebLib.Excel excel;
|
||||
protected List<ImportColumn> columns;
|
||||
private static string importFilePath = "~/DocUpload/Import";
|
||||
protected DbConnection connection;
|
||||
protected static List<string> trueValues = new List<string>() { "Y", "V", "T", "1", "YES", "TRUE" };
|
||||
|
||||
public const double MinOADate = 43831.0; // 2020/01/01
|
||||
public const double MaxOADate = 2958465.0; // 9999/12/31
|
||||
|
||||
public ImportBase()
|
||||
{
|
||||
excel = new WebLib.Excel();
|
||||
columns = new List<ImportColumn>();
|
||||
}
|
||||
|
||||
public DbConnection Connection
|
||||
{
|
||||
get { return connection; }
|
||||
set { connection = value; }
|
||||
}
|
||||
|
||||
public static string ImportFilePath
|
||||
{
|
||||
get
|
||||
{
|
||||
var fullPath = HttpContext.Current.Server.MapPath(importFilePath);
|
||||
if (!Directory.Exists(fullPath))
|
||||
{
|
||||
Directory.CreateDirectory(fullPath);
|
||||
}
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
public MemoryStream ImportExample()
|
||||
{
|
||||
var workbook = new XSSFWorkbook();
|
||||
var sheet = workbook.CreateSheet("匯入範例");
|
||||
excel.Sheet = sheet;
|
||||
int col = 0;
|
||||
foreach (var column in columns)
|
||||
{
|
||||
excel.SetCellValue(0, col, column.Title);
|
||||
excel.SetColumnWidthInChars(col, column.Width);
|
||||
col++;
|
||||
}
|
||||
MemoryStream ms = new MemoryStream();
|
||||
workbook.Write(ms);
|
||||
return ms;
|
||||
}
|
||||
|
||||
public IWorkbook LoadWorkbook(string filePath)
|
||||
{
|
||||
using (FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
if (Path.GetExtension(filePath).ToLower() == ".xls")
|
||||
{
|
||||
return new HSSFWorkbook(file);
|
||||
}
|
||||
else if (Path.GetExtension(filePath).ToLower() == ".xlsx")
|
||||
{
|
||||
return new XSSFWorkbook(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("不支援的檔案格式");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> Validate(string filePath, Type type, out int rowCount)
|
||||
{
|
||||
// 1. 開啟 Excel 檔案
|
||||
var workbook = LoadWorkbook(filePath);
|
||||
// 2. 讀取第一個工作表
|
||||
var sheet = workbook.GetSheetAt(0);
|
||||
// 3. 驗證欄位名稱
|
||||
var headerRow = sheet.GetRow(0);
|
||||
var errors = ValidateColumns(headerRow, out var columnMap);
|
||||
// 4. 驗證每一列資料
|
||||
rowCount = 0;
|
||||
for (int rowId = 1; rowId <= sheet.LastRowNum; rowId++)
|
||||
{
|
||||
var row = sheet.GetRow(rowId);
|
||||
if (row == null)
|
||||
{
|
||||
break;
|
||||
|
||||
}
|
||||
var rowErrors = ValidateRow(row, type, columnMap, out bool stop);
|
||||
foreach (var err in rowErrors)
|
||||
{
|
||||
errors.Add($"第 {rowId} 列:{err}");
|
||||
}
|
||||
rowCount++;
|
||||
if (stop)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rowCount == 0)
|
||||
{
|
||||
errors.Add("沒有任何資料列");
|
||||
}
|
||||
// 5. 回傳錯誤訊息清單
|
||||
return errors;
|
||||
}
|
||||
|
||||
public List<string> ValidateColumns(IRow headerRow, out Dictionary<string, int> columnMap)
|
||||
{
|
||||
List<string> errors = new List<string>();
|
||||
columnMap = new Dictionary<string, int>();
|
||||
|
||||
// 建立標題 → 欄位索引的對照表
|
||||
if (headerRow != null)
|
||||
{
|
||||
for (int i = 0; i <= headerRow.LastCellNum; i++)
|
||||
{
|
||||
var cell = headerRow.GetCell(i);
|
||||
if (cell == null) continue;
|
||||
string title = cell.ToString().Trim();
|
||||
if (!string.IsNullOrEmpty(title) && !columnMap.ContainsKey(title))
|
||||
{
|
||||
columnMap[title] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 確認每個必要欄位都存在於標頭列
|
||||
foreach (var column in columns)
|
||||
{
|
||||
if (!columnMap.ContainsKey(column.Title))
|
||||
{
|
||||
errors.Add($"找不到欄位「{column.Title}」");
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
public List<string> ValidateRow(IRow dataRow, Type type, Dictionary<string, int> columnMap, out bool stop)
|
||||
{
|
||||
stop = false;
|
||||
List<string> errors = new List<string>();
|
||||
|
||||
// 以欄位名稱取得每欄的值
|
||||
var cellValues = new Dictionary<string, string>();
|
||||
foreach (var column in columns)
|
||||
{
|
||||
string cellValue = "";
|
||||
if (columnMap.ContainsKey(column.Title))
|
||||
{
|
||||
var cell = dataRow.GetCell(columnMap[column.Title]);
|
||||
cellValue = cell == null ? "" : excel.CellValueToString(cell);
|
||||
}
|
||||
cellValues[column.Title] = cellValue;
|
||||
}
|
||||
|
||||
// 若所有欄位皆為空則回傳停止旗標
|
||||
bool allEmpty = true;
|
||||
foreach (var v in cellValues.Values)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(v)) { allEmpty = false; break; }
|
||||
}
|
||||
if (allEmpty)
|
||||
{
|
||||
stop = true;
|
||||
return errors;
|
||||
}
|
||||
|
||||
foreach (var column in columns)
|
||||
{
|
||||
string cellValue = cellValues[column.Title];
|
||||
if (column.Required && string.IsNullOrEmpty(cellValue))
|
||||
{
|
||||
errors.Add($"欄位「{column.Title}」為必填欄位");
|
||||
}
|
||||
|
||||
// 應用 TransformFunction(如果設定)
|
||||
if (column.TransformFunction != null)
|
||||
{
|
||||
cellValue = column.TransformFunction(cellValue)?.ToString() ?? "";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cellValue))
|
||||
{
|
||||
var property = type.GetProperty(column.FieldName);
|
||||
if (property != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var targetType = property.PropertyType;
|
||||
if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
targetType = Nullable.GetUnderlyingType(targetType);
|
||||
}
|
||||
if (targetType == typeof(DateTime))
|
||||
{
|
||||
ParseExcelDate(cellValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
Convert.ChangeType(cellValue, targetType);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
var typeName = property.PropertyType.Name;
|
||||
if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
var underlying = Nullable.GetUnderlyingType(property.PropertyType);
|
||||
typeName = $"{underlying.Name}(可空)";
|
||||
}
|
||||
errors.Add($"欄位「{column.Title}」的值「{cellValue}」應該是 {typeName} 類型".Replace("<", "<").Replace(">", ">"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
public List<T> ReadRows<T>(string filePath, int beginRow = 1) where T : new()
|
||||
{
|
||||
var workbook = LoadWorkbook(filePath);
|
||||
excel.Sheet = workbook.GetSheetAt(0);
|
||||
var result = new List<T>();
|
||||
|
||||
// 從標頭列建立欄位名稱 → 欄位索引對照表
|
||||
var columnMap = new Dictionary<string, int>();
|
||||
var headerRow = excel.Sheet.GetRow(0);
|
||||
if (headerRow != null)
|
||||
{
|
||||
for (int i = 0; i <= headerRow.LastCellNum; i++)
|
||||
{
|
||||
var cell = headerRow.GetCell(i);
|
||||
if (cell == null) continue;
|
||||
string title = cell.ToString().Trim();
|
||||
if (!string.IsNullOrEmpty(title) && !columnMap.ContainsKey(title))
|
||||
{
|
||||
columnMap[title] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int r = beginRow; r <= excel.Sheet.LastRowNum; r++)
|
||||
{
|
||||
var row = excel.Sheet.GetRow(r);
|
||||
if (row == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// 以欄位名稱取得每欄的值
|
||||
var cellValues = new Dictionary<string, string>();
|
||||
foreach (var column in columns)
|
||||
{
|
||||
string cellValue = "";
|
||||
if (columnMap.ContainsKey(column.Title))
|
||||
{
|
||||
var cell = row.GetCell(columnMap[column.Title]);
|
||||
cellValue = cell == null ? "" : excel.CellValueToString(cell);
|
||||
}
|
||||
cellValues[column.Title] = cellValue;
|
||||
}
|
||||
|
||||
// 若所有欄位皆為空則停止
|
||||
bool allEmpty = true;
|
||||
foreach (var v in cellValues.Values)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(v)) { allEmpty = false; break; }
|
||||
}
|
||||
if (allEmpty)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var item = new T();
|
||||
foreach (var column in columns)
|
||||
{
|
||||
string cellValue = cellValues[column.Title];
|
||||
|
||||
// 假設 T 有對應的屬性名稱與欄位標題相同
|
||||
var property = typeof(T).GetProperty(column.FieldName);
|
||||
if (property != null)
|
||||
{
|
||||
object valueToSet = null;
|
||||
if (column.TransformFunction != null)
|
||||
{
|
||||
valueToSet = column.TransformFunction(cellValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(cellValue))
|
||||
{
|
||||
if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
valueToSet = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 對於非 Nullable 類型,如果為空則跳過設定
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetType = property.PropertyType;
|
||||
if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
targetType = Nullable.GetUnderlyingType(targetType);
|
||||
}
|
||||
if (targetType == typeof(DateTime))
|
||||
{
|
||||
valueToSet = ParseExcelDate(cellValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
valueToSet = Convert.ChangeType(cellValue, targetType);
|
||||
}
|
||||
}
|
||||
}
|
||||
property.SetValue(item, valueToSet, null);
|
||||
}
|
||||
}
|
||||
result.Add(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static DateTime ParseExcelDate(string value)
|
||||
{
|
||||
if (DateTime.TryParse(value, out DateTime dt))
|
||||
{
|
||||
return dt;
|
||||
}
|
||||
if (double.TryParse(value, out double d) && d >= MinOADate && d <= MaxOADate)
|
||||
{
|
||||
return DateTime.FromOADate(d);
|
||||
}
|
||||
throw new FormatException($"無法將「{value}」轉換為日期");
|
||||
}
|
||||
|
||||
public virtual T SelectExisting<T>(T item) where T : new()
|
||||
{
|
||||
throw new NotImplementedException("請在子類別中實作此方法以選取現有資料");
|
||||
}
|
||||
|
||||
public virtual void InsertOne<T>(T item) where T : new()
|
||||
{
|
||||
throw new NotImplementedException("請在子類別中實作此方法以新增資料");
|
||||
}
|
||||
|
||||
public virtual void UpdateOne<T>(T newItem, T existingItem) where T : new()
|
||||
{
|
||||
throw new NotImplementedException("請在子類別中實作此方法以更新資料");
|
||||
}
|
||||
|
||||
public virtual string ImportRowErrorMessage<T>(T item, string errorMessage) where T : new()
|
||||
{
|
||||
return $"資料 {item.ToString()} 匯入失敗,錯誤原因:{errorMessage}";
|
||||
}
|
||||
|
||||
public bool Import<T>(string filePath,
|
||||
out string message,
|
||||
int beginRow = 1) where T : new()
|
||||
{
|
||||
var importErrors = new List<string>();
|
||||
message = "";
|
||||
var insertCount = 0;
|
||||
var updateCount = 0;
|
||||
|
||||
var rows = ReadRows<T>(filePath, beginRow);
|
||||
if (rows.Count == 0)
|
||||
{
|
||||
importErrors.Add("匯入檔案中沒有任何資料");
|
||||
return false;
|
||||
}
|
||||
|
||||
using (var ts = DAC.NewTransactionScope())
|
||||
{
|
||||
foreach (var row in rows)
|
||||
{
|
||||
try
|
||||
{
|
||||
var existing = SelectExisting(row);
|
||||
if (existing == null)
|
||||
{
|
||||
InsertOne(row);
|
||||
insertCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateOne(row, existing);
|
||||
updateCount++;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importErrors.Add(ImportRowErrorMessage(row, ex.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (importErrors.Count == 0)
|
||||
{
|
||||
message = $"資料匯入成功,共新增 {insertCount} 筆資料,修改 {updateCount} 筆資料。";
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
message = $"資料匯入完成,共新增 {insertCount} 筆資料,修改 {updateCount} 筆資料,但有以下錯誤:<br/>" + string.Join("<br/>", importErrors.ToArray());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 真假值轉換函數,把代表真值的字串轉換成 1,否則轉換成 0
|
||||
/// </summary>
|
||||
protected TransformFunction BoolTransform = (input) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(input)) return 0;
|
||||
var inputStr = input.ToUpper();
|
||||
return trueValues.Contains(inputStr) ? 1 : 0;
|
||||
};
|
||||
}
|
||||
|
||||
// 新增委派定義
|
||||
public delegate object TransformFunction(string input);
|
||||
|
||||
public class ImportColumn
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public int Width { get; set; }
|
||||
public string FieldName { get; set; }
|
||||
public bool Required { get; set; }
|
||||
public TransformFunction TransformFunction { get; set; }
|
||||
public ImportColumn(string title, int width, bool required = false, string fieldName = null, TransformFunction transformFunction = null)
|
||||
{
|
||||
Title = title;
|
||||
Width = width;
|
||||
Required = required;
|
||||
FieldName = fieldName ?? title;
|
||||
TransformFunction = transformFunction;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Education
|
||||
{
|
||||
public class 非講師匯入 : ImportBase
|
||||
{
|
||||
|
||||
public 非講師匯入()
|
||||
{
|
||||
columns.Add(new ImportColumn("編號", 10));
|
||||
columns.Add(new ImportColumn("姓名", 10));
|
||||
columns.Add(new ImportColumn("身分證字號", 16));
|
||||
columns.Add(new ImportColumn("藝名網名", 10));
|
||||
columns.Add(new ImportColumn("聯絡電話", 16));
|
||||
columns.Add(new ImportColumn("電子郵件", 25));
|
||||
columns.Add(new ImportColumn("戶籍地址_郵編", 10));
|
||||
columns.Add(new ImportColumn("戶籍地址_縣市", 10));
|
||||
columns.Add(new ImportColumn("戶籍地址_鄉鎮市區", 10));
|
||||
columns.Add(new ImportColumn("戶籍地址_街道地址", 30));
|
||||
columns.Add(new ImportColumn("居住地址_郵編", 10));
|
||||
columns.Add(new ImportColumn("居住地址_縣市", 10));
|
||||
columns.Add(new ImportColumn("居住地址_鄉鎮市區", 10));
|
||||
columns.Add(new ImportColumn("居住地址_街道地址", 30));
|
||||
columns.Add(new ImportColumn("職務內容", 100));
|
||||
columns.Add(new ImportColumn("合作滿意度", 100));
|
||||
columns.Add(new ImportColumn("窗口", 100));
|
||||
columns.Add(new ImportColumn("是否接受邀約", 10, fieldName: "接受邀約", transformFunction: BoolTransform));
|
||||
columns.Add(new ImportColumn("聯繫狀況與報價", 100));
|
||||
columns.Add(new ImportColumn("備註", 100));
|
||||
}
|
||||
|
||||
public override T SelectExisting<T>(T item)
|
||||
{
|
||||
var row = item as 非講師Item;
|
||||
if (row == null)
|
||||
return default(T);
|
||||
var dac = new DAC_非講師(connection);
|
||||
|
||||
非講師Item existing = null;
|
||||
if (!String.IsNullOrEmpty(row.編號))
|
||||
{
|
||||
existing = dac.SelectOne(row.編號).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
existing = dac.Existing(row.姓名, row.身分證字號);
|
||||
}
|
||||
if (existing != null && existing is T tExisting)
|
||||
return tExisting;
|
||||
return default(T);
|
||||
}
|
||||
|
||||
public override void InsertOne<T>(T item)
|
||||
{
|
||||
var row = item as 非講師Item;
|
||||
if (row == null)
|
||||
return;
|
||||
var dac = new DAC_非講師(connection);
|
||||
dac.InsertOne(row);
|
||||
}
|
||||
|
||||
public override void UpdateOne<T>(T newItem, T existingItem)
|
||||
{
|
||||
var newRow = newItem as 非講師Item;
|
||||
var existingRow = existingItem as 非講師Item;
|
||||
if (newRow == null || existingRow == null)
|
||||
return;
|
||||
var dac = new DAC_非講師(connection);
|
||||
newRow.編號 = existingRow.編號;
|
||||
newRow.DB_APPNO = existingRow.DB_APPNO;
|
||||
dac.UpdateOne(newRow);
|
||||
}
|
||||
|
||||
public override string ImportRowErrorMessage<T>(T item, string errorMessage)
|
||||
{
|
||||
var row = item as 非講師Item;
|
||||
if (row != null)
|
||||
{
|
||||
return $"姓名:{row.姓名},身分證字號:{row.身分證字號},錯誤原因:{errorMessage}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.ImportRowErrorMessage(item, errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,533 @@
|
||||
using Newtonsoft.Json;
|
||||
using NPOI.SS.Formula.Functions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Linq;
|
||||
using WebLib;
|
||||
|
||||
namespace Education
|
||||
{
|
||||
/// <summary>
|
||||
/// 領據匯入類別
|
||||
/// </summary>
|
||||
public class 領據匯入 : ImportBase
|
||||
{
|
||||
public 領據匯入()
|
||||
{
|
||||
// 定義欄位
|
||||
columns.Add(new ImportColumn("活動日期", 12, required: true));
|
||||
columns.Add(new ImportColumn("身分別", 10, required: true));
|
||||
columns.Add(new ImportColumn("領款人姓名", 10, required: true));
|
||||
columns.Add(new ImportColumn("身分證字號", 15));
|
||||
columns.Add(new ImportColumn("憑證類別", 10, required: true));
|
||||
columns.Add(new ImportColumn("計畫代號", 15, required: true));
|
||||
columns.Add(new ImportColumn("活動地點", 20));
|
||||
columns.Add(new ImportColumn("活動類別", 10));
|
||||
columns.Add(new ImportColumn("付款方式", 10));
|
||||
columns.Add(new ImportColumn("活動名稱/事由", 30, fieldName: "活動名稱事由"));
|
||||
columns.Add(new ImportColumn("勞務費費用別", 15));
|
||||
columns.Add(new ImportColumn("扣繳類別", 15));
|
||||
columns.Add(new ImportColumn("單價", 10));
|
||||
columns.Add(new ImportColumn("數量", 10));
|
||||
columns.Add(new ImportColumn("單位", 10));
|
||||
columns.Add(new ImportColumn("服務費", 10));
|
||||
columns.Add(new ImportColumn("差旅票價資訊", 15, fieldName: "票價資訊"));
|
||||
columns.Add(new ImportColumn("起點", 15));
|
||||
columns.Add(new ImportColumn("訖點", 15));
|
||||
columns.Add(new ImportColumn("捷運/公車", 10, fieldName: "捷運公車"));
|
||||
columns.Add(new ImportColumn("火車", 10));
|
||||
columns.Add(new ImportColumn("計程車", 10));
|
||||
columns.Add(new ImportColumn("高鐵/飛機", 10, fieldName: "高鐵飛機"));
|
||||
columns.Add(new ImportColumn("自行開車", 10));
|
||||
columns.Add(new ImportColumn("過路費/停車費", 10, fieldName: "過路費停車費"));
|
||||
columns.Add(new ImportColumn("住宿費", 10));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 進行額外的資料驗證(欄位內容檢查)
|
||||
/// </summary>
|
||||
public List<string> ValidateContent(string filePath)
|
||||
{
|
||||
List<string> errors = new List<string>();
|
||||
var workbook = LoadWorkbook(filePath);
|
||||
var sheet = workbook.GetSheetAt(0);
|
||||
|
||||
// 從標頭列建立欄位名稱 → 欄位索引對照表
|
||||
var columnMap = new Dictionary<string, int>();
|
||||
var headerRow = sheet.GetRow(0);
|
||||
if (headerRow != null)
|
||||
{
|
||||
for (int i = 0; i <= headerRow.LastCellNum; i++)
|
||||
{
|
||||
var cell = headerRow.GetCell(i);
|
||||
if (cell == null) continue;
|
||||
string title = cell.ToString().Trim();
|
||||
if (!string.IsNullOrEmpty(title) && !columnMap.ContainsKey(title))
|
||||
columnMap[title] = i;
|
||||
}
|
||||
}
|
||||
|
||||
// 差旅費數值欄位清單
|
||||
var travelFeeColumns = new List<string> { "捷運/公車", "火車", "計程車", "高鐵/飛機", "自行開車", "過路費/停車費", "住宿費" };
|
||||
|
||||
var dac講師 = new DAC_講師(connection);
|
||||
var dac非講師 = new DAC_非講師(connection);
|
||||
|
||||
for (int rowId = 1; rowId <= sheet.LastRowNum; rowId++)
|
||||
{
|
||||
var row = sheet.GetRow(rowId);
|
||||
if (row == null)
|
||||
break;
|
||||
|
||||
// 以欄位名稱取得每欄的值
|
||||
var cellValues = new Dictionary<string, string>();
|
||||
foreach (var column in columns)
|
||||
{
|
||||
string cellValue = "";
|
||||
if (columnMap.ContainsKey(column.Title))
|
||||
{
|
||||
var cell = row.GetCell(columnMap[column.Title]);
|
||||
cellValue = cell == null ? "" : excel.CellValueToString(cell);
|
||||
}
|
||||
cellValues[column.Title] = cellValue;
|
||||
}
|
||||
|
||||
// 若所有欄位皆為空則停止
|
||||
bool allEmpty = true;
|
||||
foreach (var v in cellValues.Values)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(v)) { allEmpty = false; break; }
|
||||
}
|
||||
if (allEmpty)
|
||||
break;
|
||||
|
||||
// 驗證活動日期
|
||||
if (!string.IsNullOrEmpty(cellValues["活動日期"]))
|
||||
{
|
||||
// 日期要能接受兩種:字串與 Excel 裡日期
|
||||
if (!領據匯入驗證.IsValidDate(cellValues["活動日期"]))
|
||||
{
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「活動日期」:日期格式不正確,值為「{cellValues["活動日期"]}」");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「活動日期」:必填欄位不可為空");
|
||||
}
|
||||
|
||||
// 驗證身分別
|
||||
if (string.IsNullOrEmpty(cellValues["身分別"]))
|
||||
{
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「身分別」:必填欄位不可為空");
|
||||
}
|
||||
else if (cellValues["身分別"] != "講師" && cellValues["身分別"] != "非講師")
|
||||
{
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「身分別」:只允許「講師」或「非講師」,實際值為「{cellValues["身分別"]}」");
|
||||
}
|
||||
|
||||
// 驗證領款人姓名
|
||||
if (string.IsNullOrEmpty(cellValues["領款人姓名"]))
|
||||
{
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「領款人姓名」:必填欄位不可為空");
|
||||
}
|
||||
else
|
||||
{
|
||||
var 講師_非講師編號 = string.Empty;
|
||||
if (cellValues["身分別"] == "講師")
|
||||
{
|
||||
var t = dac講師.Select(false, cellValues["領款人姓名"], null, null)
|
||||
.Where(x => string.Compare(x.姓名, cellValues["領款人姓名"]) == 0
|
||||
&& string.Compare(x.身份證字號, cellValues["身分證字號"]) == 0)
|
||||
.FirstOrDefault();
|
||||
if (t != null)
|
||||
講師_非講師編號 = t.編號.ToString();
|
||||
}
|
||||
else if (cellValues["身分別"] == "非講師")
|
||||
{
|
||||
var t = dac非講師.Select(false, null, cellValues["領款人姓名"], null, null, null)
|
||||
.Where(x => string.Compare(x.姓名, cellValues["領款人姓名"]) == 0
|
||||
&& string.Compare(x.身分證字號, cellValues["身分證字號"]) == 0)
|
||||
.FirstOrDefault();
|
||||
if (t != null)
|
||||
講師_非講師編號 = t.編號;
|
||||
}
|
||||
|
||||
if (講師_非講師編號 == string.Empty)
|
||||
{
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「領款人姓名」:不存在於系統內");
|
||||
}
|
||||
}
|
||||
|
||||
// 驗證憑證類別
|
||||
if (string.IsNullOrEmpty(cellValues["憑證類別"]))
|
||||
{
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「憑證類別」:必填欄位不可為空");
|
||||
}
|
||||
else if (!領據匯入驗證.IsValidProofType(cellValues["憑證類別"]))
|
||||
{
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「憑證類別」:無效的值「{cellValues["憑證類別"]}」,允許值為「領據」、「發票」、「收據」");
|
||||
}
|
||||
|
||||
// 驗證計畫代號
|
||||
if (string.IsNullOrEmpty(cellValues["計畫代號"]))
|
||||
{
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「計畫代號」:必填欄位不可為空");
|
||||
}
|
||||
else
|
||||
{
|
||||
var planCode = cellValues["計畫代號"];
|
||||
bool isTYCode = System.Text.RegularExpressions.Regex.IsMatch(planCode, @"^TY\d{4}$");
|
||||
if (!isTYCode && !領據匯入驗證.IsValidProjectCode(planCode))
|
||||
{
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「計畫代號」:計畫代號「{planCode}」不存在於系統中");
|
||||
}
|
||||
}
|
||||
|
||||
// 驗證付款方式(選填)
|
||||
if (!string.IsNullOrEmpty(cellValues["付款方式"]) && !領據匯入驗證.IsValidPaymentMethod(cellValues["付款方式"]))
|
||||
{
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「付款方式」:無效的值「{cellValues["付款方式"]}」,允許值為「匯款」、「支票」、「現金」");
|
||||
}
|
||||
|
||||
// 驗證勞務費費用別(選填)
|
||||
if (!string.IsNullOrEmpty(cellValues["勞務費費用別"]) && !領據匯入驗證.IsValidLaborExpenseType(cellValues["勞務費費用別"]))
|
||||
{
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「勞務費費用別」:無效的值「{cellValues["勞務費費用別"]}」");
|
||||
}
|
||||
|
||||
// 驗證扣繳類別(選填)
|
||||
if (!string.IsNullOrEmpty(cellValues["扣繳類別"]))
|
||||
{
|
||||
cellValues["扣繳類別"] = 扣繳類別.MapValue(cellValues["扣繳類別"]);
|
||||
if (!領據匯入驗證.IsValidWithholdingCategory(cellValues["扣繳類別"]))
|
||||
{
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「扣繳類別」:無效的值「{cellValues["扣繳類別"]}」");
|
||||
}
|
||||
}
|
||||
|
||||
// 驗證扣繳類別於費用別的對應關係
|
||||
if (cellValues["勞務費費用別"] == "演講費" || cellValues["勞務費費用別"] == "撰稿費" && cellValues["扣繳類別"] == "50")
|
||||
{
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「扣繳類別」:扣繳類別只能是執行業務所得");
|
||||
}
|
||||
|
||||
// 驗證單價、數量、服務費(選填)
|
||||
int 單價 = 0;
|
||||
decimal 數量 = 0;
|
||||
bool 有勞務費金額 = false;
|
||||
|
||||
if (!string.IsNullOrEmpty(cellValues["單價"]))
|
||||
{
|
||||
if (!領據匯入驗證.IsValidDecimal(cellValues["單價"]))
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「單價」:應為數值,實際值為「{cellValues["單價"]}」");
|
||||
else
|
||||
{
|
||||
單價 = DAC.GetInt32(cellValues["單價"]);
|
||||
有勞務費金額 = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cellValues["數量"]))
|
||||
{
|
||||
if (!領據匯入驗證.IsValidDecimal(cellValues["數量"]))
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「數量」:應為數值,實際值為「{cellValues["數量"]}」");
|
||||
else
|
||||
{
|
||||
數量 = DAC.GetDecimal(cellValues["數量"]);
|
||||
有勞務費金額 = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cellValues["服務費"]))
|
||||
{
|
||||
if (!領據匯入驗證.IsValidDecimal(cellValues["服務費"]))
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「服務費」:應為數值,實際值為「{cellValues["服務費"]}」");
|
||||
else
|
||||
有勞務費金額 = true;
|
||||
}
|
||||
|
||||
// 有勞務費金額但費用別或單位未填
|
||||
if (有勞務費金額 && string.IsNullOrEmpty(cellValues["勞務費費用別"]))
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「勞務費費用別」:已輸入金額,此欄位不可為空");
|
||||
if (有勞務費金額 && string.IsNullOrEmpty(cellValues["單位"]))
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「單位」:已輸入金額,此欄位不可為空");
|
||||
|
||||
// 合計金額驗證
|
||||
int 合計金額 = (int)((decimal)單價 * 數量);
|
||||
if (cellValues["扣繳類別"] == "50" && 合計金額 >= PublicVariable.領據基本工資) {
|
||||
errors.Add($"第 {rowId + 1} 列:薪資不得超過基本工資 {PublicVariable.領據基本工資}(含),超過請拆成不同日期的領據");
|
||||
}
|
||||
if ((cellValues["扣繳類別"].StartsWith("9A") || cellValues["扣繳類別"].StartsWith("9B")) && 合計金額 >= PublicVariable.領據執行業務所得課稅門檻) {
|
||||
errors.Add($"第 {rowId + 1} 列:執行業務所得不得超過 {PublicVariable.領據執行業務所得課稅門檻}(含),超過請拆成不同日期的領據");
|
||||
}
|
||||
|
||||
// 驗證差旅費的數值欄位
|
||||
foreach (var colTitle in travelFeeColumns)
|
||||
{
|
||||
if (cellValues.ContainsKey(colTitle) && !string.IsNullOrEmpty(cellValues[colTitle]) && !領據匯入驗證.IsValidDecimal(cellValues[colTitle]))
|
||||
{
|
||||
errors.Add($"第 {rowId + 1} 列,欄位「{colTitle}」:應為數值,實際值為「{cellValues[colTitle]}」");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 驗證重複資料
|
||||
/// - 同一檔案內重複(允許匯入,但回傳警告)
|
||||
/// - 本次匯入資料與資料庫現有資料重複的總筆數超過 5 筆(不允許匯入,回傳錯誤)
|
||||
/// 重複條件:活動日期 + 領款人姓名 + 計畫代號 + 活動名稱/事由
|
||||
/// </summary>
|
||||
public void ValidateDuplicates(string filePath, DbConnection conn, out List<string> errors, out List<string> warnings)
|
||||
{
|
||||
errors = new List<string>();
|
||||
warnings = new List<string>();
|
||||
|
||||
var workbook = LoadWorkbook(filePath);
|
||||
var sheet = workbook.GetSheetAt(0);
|
||||
|
||||
// 建立欄位名稱 → 欄位索引對照表
|
||||
var columnMap = new Dictionary<string, int>();
|
||||
var headerRow = sheet.GetRow(0);
|
||||
if (headerRow != null)
|
||||
{
|
||||
for (int i = 0; i <= headerRow.LastCellNum; i++)
|
||||
{
|
||||
var cell = headerRow.GetCell(i);
|
||||
if (cell == null) continue;
|
||||
string title = cell.ToString().Trim();
|
||||
if (!string.IsNullOrEmpty(title) && !columnMap.ContainsKey(title))
|
||||
columnMap[title] = i;
|
||||
}
|
||||
}
|
||||
|
||||
// 收集每一列的鍵值:key → List<int> Excel 顯示列號
|
||||
var fileKeyRows = new Dictionary<string, List<int>>();
|
||||
|
||||
for (int rowId = 1; rowId <= sheet.LastRowNum; rowId++)
|
||||
{
|
||||
var row = sheet.GetRow(rowId);
|
||||
if (row == null) break;
|
||||
|
||||
string GetCellValue(string colTitle)
|
||||
{
|
||||
if (!columnMap.ContainsKey(colTitle)) return "";
|
||||
var c = row.GetCell(columnMap[colTitle]);
|
||||
return c == null ? "" : excel.CellValueToString(c).Trim();
|
||||
}
|
||||
|
||||
string 活動日期Raw = GetCellValue("活動日期");
|
||||
string 領款人姓名 = GetCellValue("領款人姓名");
|
||||
string 計畫代號 = GetCellValue("計畫代號");
|
||||
string 活動名稱事由 = GetCellValue("活動名稱/事由");
|
||||
|
||||
// 若整列為空則停止
|
||||
if (string.IsNullOrEmpty(活動日期Raw) && string.IsNullOrEmpty(領款人姓名) && string.IsNullOrEmpty(計畫代號))
|
||||
break;
|
||||
|
||||
// 重複鍵需要四個欄位都有值才做比對
|
||||
if (string.IsNullOrEmpty(活動日期Raw) || string.IsNullOrEmpty(領款人姓名) || string.IsNullOrEmpty(計畫代號))
|
||||
continue;
|
||||
|
||||
string key = string.Format("{0}|{1}|{2}|{3}", 活動日期Raw, 領款人姓名, 計畫代號, 活動名稱事由);
|
||||
|
||||
if (!fileKeyRows.ContainsKey(key))
|
||||
fileKeyRows[key] = new List<int>();
|
||||
fileKeyRows[key].Add(rowId + 1); // +1 因為 rowId=1 對應 Excel 第 2 列(含標頭)
|
||||
}
|
||||
|
||||
// ── 1. 檔案內重複(警告,不阻擋)────────────────────────────────
|
||||
foreach (var kv in fileKeyRows)
|
||||
{
|
||||
if (kv.Value.Count <= 1) continue;
|
||||
|
||||
string[] parts = kv.Key.Split('|');
|
||||
warnings.Add(string.Format(
|
||||
"檔案內重複:活動日期「{0}」、領款人「{1}」、計畫代號「{2}」、活動名稱事由「{3}」出現於第 {4} 列(共 {5} 筆)",
|
||||
parts[0], parts[1], parts[2], parts[3],
|
||||
string.Join("、", kv.Value.ConvertAll(r => r.ToString()).ToArray()),
|
||||
kv.Value.Count));
|
||||
}
|
||||
|
||||
// ── 2. 與資料庫現有資料比對 ──────────────────────────────────────
|
||||
// 每個唯一鍵查一次 DB,累計所有重複筆數;每筆有重複的都顯示給使用者
|
||||
var dac = new DAC_領據(conn);
|
||||
int totalDbDuplicates = 0;
|
||||
var dbDuplicateMessages = new List<string>();
|
||||
|
||||
foreach (var kv in fileKeyRows)
|
||||
{
|
||||
string[] parts = kv.Key.Split('|');
|
||||
if (!DateTime.TryParse(parts[0], out DateTime 活動日期)) continue;
|
||||
|
||||
string 領款人姓名DB = parts[1];
|
||||
string 計畫代號DB = parts[2];
|
||||
string 活動名稱事由DB = parts[3];
|
||||
|
||||
int dbCount = dac.CountDuplicates(活動日期, 領款人姓名DB, 計畫代號DB, 活動名稱事由DB);
|
||||
if (dbCount <= 0) continue;
|
||||
|
||||
totalDbDuplicates += dbCount;
|
||||
dbDuplicateMessages.Add(string.Format(
|
||||
"與現有資料重複:活動日期「{0}」、領款人「{1}」、計畫代號「{2}」、活動名稱事由「{3}」已有 {4} 筆",
|
||||
parts[0], 領款人姓名DB, 計畫代號DB, 活動名稱事由DB, dbCount));
|
||||
}
|
||||
|
||||
// 不論是否超過 5 筆,有重複的都要顯示;超過 5 筆則放入 errors 阻擋匯入
|
||||
if (dbDuplicateMessages.Count > 0)
|
||||
{
|
||||
if (totalDbDuplicates > 5)
|
||||
{
|
||||
errors.Add(string.Format("與現有資料重複共 {0} 筆(超過 5 筆),不允許匯入:", totalDbDuplicates));
|
||||
errors.AddRange(dbDuplicateMessages);
|
||||
}
|
||||
else
|
||||
{
|
||||
warnings.Add(string.Format("與現有資料重複共 {0} 筆:", totalDbDuplicates));
|
||||
warnings.AddRange(dbDuplicateMessages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void InsertOne<T>(T item)
|
||||
{
|
||||
var row = item as 領據匯入Item;
|
||||
|
||||
if (row == null)
|
||||
return;
|
||||
|
||||
using (var ts = DAC.NewTransactionScope())
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. 插入領據資料
|
||||
var dac領據 = new DAC_領據(connection);
|
||||
var dac講師 = new DAC_講師(connection);
|
||||
var dac非講師 = new DAC_非講師(connection);
|
||||
var dacBPAA = new DAC_BPAA();
|
||||
var 領據Item = new 領據Item();
|
||||
|
||||
var 講師_非講師編號 = string.Empty;
|
||||
if (row.身分別 == "講師")
|
||||
{
|
||||
var t = dac講師.Select(false, row.領款人姓名, null, null)
|
||||
.Where(x => string.Compare(x.姓名, row.領款人姓名) == 0
|
||||
&& string.Compare(x.身份證字號, row.身分證字號) == 0)
|
||||
.FirstOrDefault();
|
||||
if (t != null)
|
||||
講師_非講師編號 = t.編號.ToString();
|
||||
}
|
||||
else if (row.身分別 == "非講師")
|
||||
{
|
||||
var t = dac非講師.Select(false, null, row.領款人姓名, null, null, null)
|
||||
.Where(x => string.Compare(x.姓名, row.領款人姓名) == 0
|
||||
&& string.Compare(x.身分證字號, row.身分證字號) == 0)
|
||||
.FirstOrDefault();
|
||||
if (t != null)
|
||||
講師_非講師編號 = t.編號;
|
||||
}
|
||||
|
||||
if (講師_非講師編號 == string.Empty)
|
||||
{
|
||||
throw new Exception($"插入資料時發生錯誤: {row.領款人姓名} 不存在於系統內");
|
||||
}
|
||||
|
||||
var dataTable = dacBPAA.GetBpaaWithTYPlusNone();
|
||||
foreach (DataRow r in dataTable.Rows)
|
||||
{
|
||||
if (r["BPPYN"].ToString() == row.計畫代號)
|
||||
{
|
||||
領據Item.計畫名稱 = DAC.GetString(r["BPPYM"]);
|
||||
}
|
||||
}
|
||||
|
||||
領據Item.活動日期 = row.活動日期.Value;
|
||||
領據Item.身分別 = row.身分別;
|
||||
領據Item.講師_非講師編號 = 講師_非講師編號;
|
||||
領據Item.領款人姓名 = row.領款人姓名;
|
||||
領據Item.身分證字號 = row.身分證字號;
|
||||
領據Item.憑證類別 = row.憑證類別;
|
||||
領據Item.計畫代號 = row.計畫代號;
|
||||
領據Item.活動地點 = row.活動地點;
|
||||
領據Item.活動類別 = row.活動類別;
|
||||
領據Item.活動名稱事由 = row.活動名稱事由;
|
||||
領據Item.付款方式 = row.付款方式;
|
||||
領據Item.製單人員 = dac領據.CurrentUserId;
|
||||
dac領據.InsertOne(領據Item);
|
||||
|
||||
int 領據編號 = 領據Item.編號;
|
||||
|
||||
// 2. 如果有勞務費資料,插入勞務費表
|
||||
if (!string.IsNullOrEmpty(row.勞務費費用別) || row.單價.HasValue || row.數量.HasValue || row.服務費.HasValue)
|
||||
{
|
||||
var dac勞務費 = new DAC_勞務費(connection);
|
||||
var 勞務費Item = new 勞務費Item();
|
||||
var a扣繳類別 = 扣繳類別.MapValue(row.扣繳類別);
|
||||
|
||||
var 扣繳稅額 = 0;
|
||||
var 扣繳稅額result = new Services.領據處理().CalcTax((int)((decimal)row.單價 * row.數量), a扣繳類別);
|
||||
if (扣繳稅額result.Success)
|
||||
{
|
||||
扣繳稅額 = DAC.GetInt32(扣繳稅額result.Data);
|
||||
}
|
||||
|
||||
勞務費Item.領據編號 = 領據編號;
|
||||
勞務費Item.費用別 = row.勞務費費用別;
|
||||
勞務費Item.扣繳類別 = a扣繳類別;
|
||||
勞務費Item.單價 = row.單價.HasValue ? row.單價.Value : 0;
|
||||
勞務費Item.數量 = row.數量.HasValue ? row.數量.Value : 0;
|
||||
勞務費Item.單位 = row.單位;
|
||||
勞務費Item.服務費 = row.服務費.HasValue ? row.服務費.Value : 0;
|
||||
勞務費Item.應扣繳稅額 = 扣繳稅額;
|
||||
dac勞務費.InsertOne(勞務費Item);
|
||||
}
|
||||
|
||||
// 3. 如果有差旅費資料,插入差旅費表
|
||||
if (!string.IsNullOrEmpty(row.差旅票價資訊) || row.捷運公車.HasValue || row.火車.HasValue ||
|
||||
row.計程車.HasValue || row.高鐵飛機.HasValue || row.自行開車.HasValue ||
|
||||
row.過路費停車費.HasValue || row.住宿費.HasValue)
|
||||
{
|
||||
var dac差旅費 = new DAC_差旅費(connection);
|
||||
var 差旅費Item = new 差旅費Item();
|
||||
差旅費Item.領據編號 = 領據編號;
|
||||
差旅費Item.費用別 = 差旅費_費用別.交通住宿費;
|
||||
差旅費Item.票價資訊 = row.差旅票價資訊;
|
||||
差旅費Item.起點 = row.起點;
|
||||
差旅費Item.訖點 = row.訖點;
|
||||
差旅費Item.捷運公車 = row.捷運公車.HasValue ? row.捷運公車.Value : 0;
|
||||
差旅費Item.火車 = row.火車.HasValue ? row.火車.Value : 0;
|
||||
差旅費Item.計程車 = row.計程車.HasValue ? row.計程車.Value : 0;
|
||||
差旅費Item.高鐵飛機 = row.高鐵飛機.HasValue ? row.高鐵飛機.Value : 0;
|
||||
差旅費Item.自行開車 = row.自行開車.HasValue ? row.自行開車.Value : 0;
|
||||
差旅費Item.過路費停車費 = row.過路費停車費.HasValue ? row.過路費停車費.Value : 0;
|
||||
差旅費Item.住宿費 = row.住宿費.HasValue ? row.住宿費.Value : 0;
|
||||
dac差旅費.InsertOne(差旅費Item);
|
||||
}
|
||||
|
||||
ts.Complete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"插入資料時發生錯誤: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ImportRowErrorMessage<T>(T item, string errorMessage)
|
||||
{
|
||||
var row = item as 領據匯入Item;
|
||||
if (row != null)
|
||||
{
|
||||
return $"領款人:{row.領款人姓名},憑證類別:{row.憑證類別},計畫代號:{row.計畫代號},錯誤原因:{errorMessage}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.ImportRowErrorMessage(item, errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Education
|
||||
{
|
||||
/// <summary>
|
||||
/// 領據匯入資料項目
|
||||
/// </summary>
|
||||
public class 領據匯入Item
|
||||
{
|
||||
public DateTime? 活動日期 { get; set; }
|
||||
public string 身分別 { get; set; }
|
||||
public string 領款人姓名 { get; set; }
|
||||
public string 身分證字號 { get; set; }
|
||||
public string 憑證類別 { get; set; }
|
||||
public string 計畫代號 { get; set; }
|
||||
public string 計畫名稱 { get; set; }
|
||||
public string 活動地點 { get; set; }
|
||||
public string 活動類別 { get; set; }
|
||||
public string 付款方式 { get; set; }
|
||||
public string 活動名稱事由 { get; set; }
|
||||
|
||||
// 勞務費欄位
|
||||
public string 勞務費費用別 { get; set; }
|
||||
public string 扣繳類別 { get; set; }
|
||||
public int? 單價 { get; set; }
|
||||
public decimal? 數量 { get; set; }
|
||||
public string 單位 { get; set; }
|
||||
public int? 服務費 { get; set; }
|
||||
|
||||
// 差旅費欄位
|
||||
public string 差旅票價資訊 { get; set; }
|
||||
public string 起點 { get; set; }
|
||||
public string 訖點 { get; set; }
|
||||
public int? 捷運公車 { get; set; }
|
||||
public int? 火車 { get; set; }
|
||||
public int? 計程車 { get; set; }
|
||||
public int? 高鐵飛機 { get; set; }
|
||||
public int? 自行開車 { get; set; }
|
||||
public int? 過路費停車費 { get; set; }
|
||||
public int? 住宿費 { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user