chore: 首次簽入 Thinkyu ASP.NET 專案
- 加入 Visual Studio / ASP.NET .gitignore - 排除建置輸出、IDE 設定、NuGet packages、大型 MSI 安裝檔
This commit is contained in:
@@ -0,0 +1,442 @@
|
||||
using NPOI.SS.UserModel;
|
||||
using NPOI.SS.Util;
|
||||
using NPOI.XSSF.UserModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml.Linq;
|
||||
using WebLib;
|
||||
|
||||
namespace Education.Report
|
||||
{
|
||||
public class 領據列印
|
||||
{
|
||||
private const string TEMPLATE_FILE_NAME = "領據列印模版.xlsx";
|
||||
private const string LABOR_SHEET_NAME = "勞務費";
|
||||
private const string TRAVEL_SHEET_NAME = "差旅費";
|
||||
|
||||
public System.IO.MemoryStream DoReport(List<Education.Services.PrintData> printDataList)
|
||||
{
|
||||
// 載入模版檔案
|
||||
string templatePath = GetTemplatePath();
|
||||
|
||||
if (!File.Exists(templatePath))
|
||||
{
|
||||
throw new FileNotFoundException(string.Format("模版檔案不存在: {0}", templatePath));
|
||||
}
|
||||
|
||||
XSSFWorkbook templateWorkbook = null;
|
||||
XSSFWorkbook outputWorkbook = null;
|
||||
System.IO.MemoryStream ms = new System.IO.MemoryStream();
|
||||
|
||||
try
|
||||
{
|
||||
// 從模版載入 workbook (唯讀)
|
||||
using (FileStream templateStream = new FileStream(templatePath, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
templateWorkbook = new XSSFWorkbook(templateStream);
|
||||
}
|
||||
|
||||
// 建立產出工作簿
|
||||
outputWorkbook = new XSSFWorkbook();
|
||||
|
||||
// 取得模版工作表
|
||||
ISheet laborTemplateSheet = templateWorkbook.GetSheet(LABOR_SHEET_NAME);
|
||||
ISheet travelTemplateSheet = templateWorkbook.GetSheet(TRAVEL_SHEET_NAME);
|
||||
|
||||
if (laborTemplateSheet == null)
|
||||
{
|
||||
throw new Exception(string.Format("模版檔案中找不到工作表: {0}", LABOR_SHEET_NAME));
|
||||
}
|
||||
if (travelTemplateSheet == null)
|
||||
{
|
||||
throw new Exception(string.Format("模版檔案中找不到工作表: {0}", TRAVEL_SHEET_NAME));
|
||||
}
|
||||
|
||||
// 處理每筆領據
|
||||
int sheetCount = 0;
|
||||
foreach (var data in printDataList)
|
||||
{
|
||||
// 勞務費單據
|
||||
if (data.Labor != null && !data.Labor.IsEmpty)
|
||||
{
|
||||
ProcessLaborSheet(outputWorkbook, laborTemplateSheet, data.Main, data.Labor, data.講師, data.非講師, ref sheetCount);
|
||||
}
|
||||
|
||||
// 差旅費單據
|
||||
if (data.Travel != null && !data.Travel.IsEmpty)
|
||||
{
|
||||
ProcessTravelSheet(outputWorkbook, travelTemplateSheet, data.Main, data.Travel, data.講師, data.非講師, ref sheetCount);
|
||||
}
|
||||
}
|
||||
|
||||
// 寫入 MemoryStream
|
||||
outputWorkbook.Write(ms);
|
||||
return ms;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (templateWorkbook != null)
|
||||
{
|
||||
templateWorkbook.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetTemplatePath()
|
||||
{
|
||||
// 模版檔案應位於應用程式根目錄下的 Template 資料夾
|
||||
string appPath = System.AppDomain.CurrentDomain.BaseDirectory;
|
||||
string templatesFolder = Path.Combine(appPath, "Template");
|
||||
return Path.Combine(templatesFolder, TEMPLATE_FILE_NAME);
|
||||
}
|
||||
|
||||
private void ProcessLaborSheet(XSSFWorkbook outputWorkbook, ISheet templateSheet, 領據Item mainData, 勞務費Item laborData, 講師 講師, 非講師Item 非講師, ref int sheetCount)
|
||||
{
|
||||
// 複製模版工作表到產出工作簿
|
||||
ISheet newSheet = CopySheetToWorkbook(outputWorkbook, templateSheet, string.Format("{0}_講師勞務費_{1}", mainData.領款人姓名, laborData.編號));
|
||||
sheetCount++;
|
||||
|
||||
// 建立替換字典
|
||||
var replacements = new Dictionary<string, string>
|
||||
{
|
||||
{ "[領據編號]", laborData.編號 },
|
||||
{ "[領款人姓名]", mainData.領款人姓名 },
|
||||
{ "[計畫名稱]", mainData.計畫名稱 },
|
||||
{ "[費用別]", laborData.費用別 },
|
||||
{ "[單價]", string.Format("{0:###,##0}", laborData.單價) },
|
||||
{ "[數量]", string.Format("{0:###,##0.##}", laborData.數量) },
|
||||
{ "[單位]", laborData.單位 },
|
||||
{ "[身分證字號]", mainData.身分證字號 },
|
||||
{ "[聯絡電話]", mainData.身分別 == "講師" ? 講師.手機 : 非講師.聯絡電話 },
|
||||
{ "[戶籍地址]", mainData.身分別 == "講師" ? 講師.戶籍地址 : 非講師.戶籍地址 },
|
||||
{ "[電子郵件]", mainData.身分別 == "講師" ? 講師.電子郵件 : 非講師.電子郵件 },
|
||||
{ "[扣繳類別]", 扣繳類別.GetName(laborData.扣繳類別) },
|
||||
{ "[合計]", string.Format("{0:###,##0}", laborData.合計金額) },
|
||||
{ "[應扣繳稅額]", string.Format("{0:###,##0}", laborData.應扣繳稅額) },
|
||||
{ "[活動名稱事由]", mainData.活動名稱事由 },
|
||||
{ "[活動日期年]", (mainData.活動日期.Year - 1911).ToString() },
|
||||
{ "[活動日期月]", mainData.活動日期.Month.ToString() },
|
||||
{ "[活動日期日]", mainData.活動日期.Day.ToString() }
|
||||
};
|
||||
|
||||
// 執行字串取代
|
||||
ReplaceInSheet(newSheet, replacements);
|
||||
}
|
||||
|
||||
private void ProcessTravelSheet(XSSFWorkbook outputWorkbook, ISheet templateSheet, 領據Item mainData, 差旅費Item travelData, 講師 講師, 非講師Item 非講師, ref int sheetCount)
|
||||
{
|
||||
// 複製模版工作表到產出工作簿
|
||||
ISheet newSheet = CopySheetToWorkbook(outputWorkbook, templateSheet, string.Format("{0}_差旅費_{1}", mainData.領款人姓名, travelData.編號));
|
||||
sheetCount++;
|
||||
|
||||
// 建立替換字典
|
||||
var replacements = new Dictionary<string, string>
|
||||
{
|
||||
{ "[領據編號]", travelData.編號 },
|
||||
{ "[計畫名稱]", mainData.計畫名稱 },
|
||||
{ "[領款人姓名]", mainData.領款人姓名 },
|
||||
{ "[起點]", travelData.起點 },
|
||||
{ "[訖點]", travelData.訖點 },
|
||||
{ "[捷公]", string.Format("{0:###,##0}", travelData.捷運公車) },
|
||||
{ "[火車]", string.Format("{0:###,##0}", travelData.火車) },
|
||||
{ "[計程]", string.Format("{0:###,##0}", travelData.計程車) },
|
||||
{ "[高飛]", string.Format("{0:###,##0}", travelData.高鐵飛機) },
|
||||
{ "[開車]", string.Format("{0:###,##0}", travelData.自行開車) },
|
||||
{ "[過停]", string.Format("{0:###,##0}", travelData.過路費停車費) },
|
||||
{ "[住宿]", string.Format("{0:###,##0}", travelData.住宿費) },
|
||||
{ "[合計]", string.Format("{0:###,##0}", travelData.合計) },
|
||||
{ "[活動名稱事由]", mainData.活動名稱事由 },
|
||||
{ "[活動日期年]", (mainData.活動日期.Year - 1911).ToString() },
|
||||
{ "[活動日期月]", mainData.活動日期.Month.ToString() },
|
||||
{ "[活動日期日]", mainData.活動日期.Day.ToString() }
|
||||
};
|
||||
|
||||
// 執行字串取代
|
||||
ReplaceInSheet(newSheet, replacements);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 複製工作表到另一個 workbook 中
|
||||
/// </summary>
|
||||
private ISheet CopySheetToWorkbook(XSSFWorkbook targetWorkbook, ISheet sourceSheet, string newSheetName)
|
||||
{
|
||||
ISheet newSheet = targetWorkbook.CreateSheet(newSheetName);
|
||||
|
||||
// 建立樣式快取和字體快取
|
||||
Dictionary<int, ICellStyle> styleCache = new Dictionary<int, ICellStyle>();
|
||||
Dictionary<string, IFont> fontCache = new Dictionary<string, IFont>();
|
||||
|
||||
// 複製所有行
|
||||
for (int rowIndex = 0; rowIndex <= sourceSheet.LastRowNum; rowIndex++)
|
||||
{
|
||||
IRow sourceRow = sourceSheet.GetRow(rowIndex);
|
||||
if (sourceRow == null)
|
||||
continue;
|
||||
|
||||
IRow newRow = newSheet.CreateRow(rowIndex);
|
||||
CopyRow(targetWorkbook, sourceRow, newRow, styleCache, fontCache);
|
||||
}
|
||||
|
||||
// 複製列寬(只複製來源工作表實際有內容的欄位)
|
||||
int maxCol = 0;
|
||||
for (int r = 0; r <= sourceSheet.LastRowNum; r++)
|
||||
{
|
||||
var row = sourceSheet.GetRow(r);
|
||||
if (row != null && row.LastCellNum > maxCol)
|
||||
maxCol = row.LastCellNum;
|
||||
}
|
||||
for (int colIndex = 0; colIndex < maxCol; colIndex++)
|
||||
{
|
||||
newSheet.SetColumnWidth(colIndex, sourceSheet.GetColumnWidth(colIndex));
|
||||
}
|
||||
|
||||
// 複製合併儲存格
|
||||
CopyMergedRegions(sourceSheet, newSheet);
|
||||
|
||||
// 複製列印設定(加上驗證)
|
||||
CopyPrintSetup(sourceSheet, newSheet);
|
||||
|
||||
// 複製頁面邊界: 天地上下左右
|
||||
newSheet.SetMargin(MarginType.TopMargin, sourceSheet.GetMargin(MarginType.TopMargin));
|
||||
newSheet.SetMargin(MarginType.BottomMargin, sourceSheet.GetMargin(MarginType.BottomMargin));
|
||||
newSheet.SetMargin(MarginType.LeftMargin, sourceSheet.GetMargin(MarginType.LeftMargin));
|
||||
newSheet.SetMargin(MarginType.RightMargin, sourceSheet.GetMargin(MarginType.RightMargin));
|
||||
newSheet.SetMargin(MarginType.HeaderMargin, sourceSheet.GetMargin(MarginType.HeaderMargin));
|
||||
newSheet.SetMargin(MarginType.FooterMargin, sourceSheet.GetMargin(MarginType.FooterMargin));
|
||||
|
||||
// 複製顯示格線設定
|
||||
newSheet.DisplayGridlines = sourceSheet.DisplayGridlines;
|
||||
newSheet.IsPrintGridlines = sourceSheet.IsPrintGridlines;
|
||||
|
||||
return newSheet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 複製列印設定(帶驗證)
|
||||
/// </summary>
|
||||
private void CopyPrintSetup(ISheet sourceSheet, ISheet targetSheet)
|
||||
{
|
||||
if (sourceSheet.PrintSetup == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
IPrintSetup sourcePrintSetup = sourceSheet.PrintSetup;
|
||||
IPrintSetup targetPrintSetup = targetSheet.PrintSetup;
|
||||
|
||||
// 複製方向
|
||||
targetPrintSetup.Landscape = sourcePrintSetup.Landscape;
|
||||
|
||||
// 複製紙張大小
|
||||
targetPrintSetup.PaperSize = sourcePrintSetup.PaperSize;
|
||||
|
||||
// 複製符合頁面設定
|
||||
// targetPrintSetup.FitWidth = sourcePrintSetup.FitWidth;
|
||||
// targetPrintSetup.FitHeight = sourcePrintSetup.FitHeight;
|
||||
|
||||
// 複製水平置中與垂直置中
|
||||
targetSheet.HorizontallyCenter = sourceSheet.HorizontallyCenter;
|
||||
targetSheet.VerticallyCenter = sourceSheet.VerticallyCenter;
|
||||
|
||||
// 將工作表放入單一頁面
|
||||
// targetSheet.FitToPage = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 如果複製列印設定失敗,記錄但不中斷流程
|
||||
System.Diagnostics.Debug.WriteLine(string.Format("複製列印設定失敗: {0}", ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 複製合併儲存格區域
|
||||
/// </summary>
|
||||
private void CopyMergedRegions(ISheet sourceSheet, ISheet targetSheet)
|
||||
{
|
||||
int mergedRegionCount = sourceSheet.NumMergedRegions;
|
||||
for (int i = 0; i < mergedRegionCount; i++)
|
||||
{
|
||||
CellRangeAddress mergedRegion = sourceSheet.GetMergedRegion(i);
|
||||
if (mergedRegion != null)
|
||||
{
|
||||
targetSheet.AddMergedRegion(new CellRangeAddress(
|
||||
mergedRegion.FirstRow,
|
||||
mergedRegion.LastRow,
|
||||
mergedRegion.FirstColumn,
|
||||
mergedRegion.LastColumn
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 複製行及其樣式到目標工作簿(使用快取避免重複建立樣式)
|
||||
/// </summary>
|
||||
private void CopyRow(XSSFWorkbook targetWorkbook, IRow sourceRow, IRow targetRow,
|
||||
Dictionary<int, ICellStyle> styleCache, Dictionary<string, IFont> fontCache)
|
||||
{
|
||||
targetRow.Height = sourceRow.Height;
|
||||
|
||||
for (int colIndex = 0; colIndex < sourceRow.LastCellNum; colIndex++)
|
||||
{
|
||||
ICell sourceCell = sourceRow.GetCell(colIndex);
|
||||
if (sourceCell == null)
|
||||
continue;
|
||||
|
||||
ICell targetCell = targetRow.CreateCell(colIndex);
|
||||
|
||||
// 複製樣式(使用快取)
|
||||
if (sourceCell.CellStyle != null)
|
||||
{
|
||||
short styleIndex = sourceCell.CellStyle.Index;
|
||||
|
||||
ICellStyle targetStyle;
|
||||
if (!styleCache.TryGetValue(styleIndex, out targetStyle))
|
||||
{
|
||||
// 第一次遇到這個樣式,建立並快取
|
||||
targetStyle = CreateCellStyle(targetWorkbook, sourceCell.CellStyle, fontCache);
|
||||
styleCache[styleIndex] = targetStyle;
|
||||
}
|
||||
|
||||
targetCell.CellStyle = targetStyle;
|
||||
}
|
||||
|
||||
// 複製值
|
||||
CopyCellValue(sourceCell, targetCell);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 建立儲存格樣式(包含字體)
|
||||
/// </summary>
|
||||
private ICellStyle CreateCellStyle(XSSFWorkbook targetWorkbook, ICellStyle sourceStyle, Dictionary<string, IFont> fontCache)
|
||||
{
|
||||
ICellStyle newStyle = targetWorkbook.CreateCellStyle();
|
||||
|
||||
// 先複製基本樣式(對齊、邊框、背景等)
|
||||
newStyle.Alignment = sourceStyle.Alignment;
|
||||
newStyle.VerticalAlignment = sourceStyle.VerticalAlignment;
|
||||
newStyle.WrapText = sourceStyle.WrapText;
|
||||
newStyle.BorderTop = sourceStyle.BorderTop;
|
||||
newStyle.BorderBottom = sourceStyle.BorderBottom;
|
||||
newStyle.BorderLeft = sourceStyle.BorderLeft;
|
||||
newStyle.BorderRight = sourceStyle.BorderRight;
|
||||
newStyle.TopBorderColor = sourceStyle.TopBorderColor;
|
||||
newStyle.BottomBorderColor = sourceStyle.BottomBorderColor;
|
||||
newStyle.LeftBorderColor = sourceStyle.LeftBorderColor;
|
||||
newStyle.RightBorderColor = sourceStyle.RightBorderColor;
|
||||
newStyle.FillPattern = sourceStyle.FillPattern;
|
||||
newStyle.FillForegroundColor = sourceStyle.FillForegroundColor;
|
||||
newStyle.FillBackgroundColor = sourceStyle.FillBackgroundColor;
|
||||
newStyle.DataFormat = sourceStyle.DataFormat;
|
||||
|
||||
// 複製字體(使用快取,但不包含底線)
|
||||
XSSFCellStyle xssfSourceStyle = sourceStyle as XSSFCellStyle;
|
||||
if (xssfSourceStyle != null)
|
||||
{
|
||||
XSSFFont sourceFont = xssfSourceStyle.GetFont() as XSSFFont;
|
||||
if (sourceFont != null)
|
||||
{
|
||||
// 建立字體的唯一鍵
|
||||
string fontKey = string.Format("{0}_{1}_{2}_{3}_{4}_{5}",
|
||||
sourceFont.FontName ?? "",
|
||||
sourceFont.FontHeightInPoints,
|
||||
sourceFont.IsBold,
|
||||
sourceFont.IsItalic,
|
||||
sourceFont.IsStrikeout,
|
||||
sourceFont.Color);
|
||||
|
||||
IFont targetFont;
|
||||
if (!fontCache.TryGetValue(fontKey, out targetFont))
|
||||
{
|
||||
// 第一次遇到這個字體,建立並快取
|
||||
targetFont = targetWorkbook.CreateFont();
|
||||
targetFont.FontName = sourceFont.FontName;
|
||||
targetFont.FontHeightInPoints = sourceFont.FontHeightInPoints;
|
||||
targetFont.IsBold = sourceFont.IsBold;
|
||||
targetFont.IsItalic = sourceFont.IsItalic;
|
||||
targetFont.IsStrikeout = sourceFont.IsStrikeout;
|
||||
targetFont.Color = sourceFont.Color;
|
||||
// 不複製底線:targetFont.Underline = sourceFont.Underline;
|
||||
|
||||
fontCache[fontKey] = targetFont;
|
||||
}
|
||||
|
||||
newStyle.SetFont(targetFont);
|
||||
}
|
||||
}
|
||||
|
||||
return newStyle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 複製儲存格的值
|
||||
/// </summary>
|
||||
private void CopyCellValue(ICell sourceCell, ICell targetCell)
|
||||
{
|
||||
switch (sourceCell.CellType)
|
||||
{
|
||||
case CellType.String:
|
||||
targetCell.SetCellValue(sourceCell.StringCellValue);
|
||||
break;
|
||||
case CellType.Numeric:
|
||||
if (DateUtil.IsCellDateFormatted(sourceCell))
|
||||
{
|
||||
targetCell.SetCellValue(sourceCell.DateCellValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetCell.SetCellValue(sourceCell.NumericCellValue);
|
||||
}
|
||||
break;
|
||||
case CellType.Boolean:
|
||||
targetCell.SetCellValue(sourceCell.BooleanCellValue);
|
||||
break;
|
||||
case CellType.Formula:
|
||||
targetCell.SetCellFormula(sourceCell.CellFormula);
|
||||
break;
|
||||
case CellType.Blank:
|
||||
targetCell.SetCellType(CellType.Blank);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在工作表中執行字串取代
|
||||
/// </summary>
|
||||
private void ReplaceInSheet(ISheet sheet, Dictionary<string, string> replacements)
|
||||
{
|
||||
for (int rowIndex = 0; rowIndex <= sheet.LastRowNum; rowIndex++)
|
||||
{
|
||||
IRow row = sheet.GetRow(rowIndex);
|
||||
if (row == null)
|
||||
continue;
|
||||
|
||||
for (int colIndex = 0; colIndex < row.LastCellNum; colIndex++)
|
||||
{
|
||||
ICell cell = row.GetCell(colIndex);
|
||||
if (cell == null || cell.CellType != CellType.String)
|
||||
continue;
|
||||
|
||||
string cellValue = cell.StringCellValue;
|
||||
string newValue = cellValue;
|
||||
|
||||
// 逐個替換
|
||||
foreach (var kvp in replacements)
|
||||
{
|
||||
newValue = newValue.Replace(kvp.Key, kvp.Value ?? "");
|
||||
}
|
||||
|
||||
// 如果有變化,更新單元格
|
||||
if (newValue != cellValue)
|
||||
{
|
||||
cell.SetCellValue(newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user