443 lines
19 KiB
C#
443 lines
19 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|