From c3f2d905cf5fe6250383faeca29340003c003eaf Mon Sep 17 00:00:00 2001 From: "earo.lau" Date: Sun, 21 Sep 2025 15:40:06 +0800 Subject: [PATCH] =?UTF-8?q?=E9=83=A8=E9=97=A8=E7=BB=9F=E8=AE=A1=E5=92=8C?= =?UTF-8?q?=E4=BA=A7=E5=93=81=E6=B1=87=E6=80=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KellyReport_D.csproj | 2 +- Utils/WorkBookUtils.cs | 313 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 310 insertions(+), 5 deletions(-) diff --git a/KellyReport_D.csproj b/KellyReport_D.csproj index cb2fa5f..32343c0 100644 --- a/KellyReport_D.csproj +++ b/KellyReport_D.csproj @@ -34,7 +34,7 @@ publish\ zh-chs - 1.0.0.2 + 1.0.0.4 true true 7 diff --git a/Utils/WorkBookUtils.cs b/Utils/WorkBookUtils.cs index 556ca51..0c279a3 100644 --- a/Utils/WorkBookUtils.cs +++ b/Utils/WorkBookUtils.cs @@ -38,6 +38,9 @@ namespace KellyReport_D.Utils // 更新业务预估表 UpdateEstimatedTable(importContext); + UpdateDeptartmentSummary(importContext); + + GenProductSummarySheet(importContext); } private static void UpdateEstimatedTable(ImportContext importContext) @@ -523,9 +526,9 @@ namespace KellyReport_D.Utils { SalesName = rawSalesDetailData[i, 1]?.ToString() ?? "UNKNOWN", Month = rawSalesDetailData[i, 2]?.ToString() ?? "", - InvoiceDate = rawSalesDetailData[i, 3] is double invoiceDate ? DateTime.FromOADate(invoiceDate) : (DateTime?)null, + DeliveryDate = rawSalesDetailData[i, 3] is double invoiceDate ? DateTime.FromOADate(invoiceDate) : (DateTime?)null, OrderNumber = rawSalesDetailData[i, 4]?.ToString() ?? "", - DeliveryDate = rawSalesDetailData[i, 5] is double deliveryDate ? DateTime.FromOADate(deliveryDate) : (DateTime?)null, + InvoiceDate = rawSalesDetailData[i, 5] != null ? DateTime.Parse(rawSalesDetailData[i, 5]?.ToString()) : (DateTime?)null, InvoiceNumber = rawSalesDetailData[i, 6]?.ToString() ?? "", ClientName = rawSalesDetailData[i, 7]?.ToString() ?? "", ProductNameEN = rawSalesDetailData[i, 8]?.ToString() ?? "", @@ -562,7 +565,7 @@ namespace KellyReport_D.Utils return null; } - public static async void GenForecast(System.Windows.Forms.Button button) + public static void GenForecast(System.Windows.Forms.Button button) { String originLabel = button.Text; if (button != null) @@ -580,7 +583,8 @@ namespace KellyReport_D.Utils // 获取「銷售年度預估」工作表 ReadSalesForecastData(context); // 获取「產品年度預估」工作表 - ReadProductForecastData(context); + //ReadProductForecastData(context); + GenDepartmentSummary(context); GenBussenessSheet(context); } @@ -839,5 +843,306 @@ namespace KellyReport_D.Utils context.SalesForecastList = salesForecastInputList; context.MonthData = monthData; } + + public static void GenProductSummarySheet(ImportContext context) + { + var app = context.Application; + + string sheetName = $"產品年度預估"; + Worksheet productSheet = CreateSheetIfNotExisted(app, sheetName); + productSheet.Activate(); + + var yearList = context.SalesDetails + .Where(x => x.InvoiceDate.HasValue) + .Select(x => x.InvoiceDate.Value.Year) + .Distinct() + .ToList(); + + yearList.ForEach(yearNumber => + { + Range usedRange = productSheet.UsedRange; + + Range turnOverRange = usedRange.Find(What: $"{yearNumber}-Turnover", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); + if (turnOverRange == null) + { + Range estimateRange = usedRange.Find(What: $"{yearNumber}-Estimate", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); + + int insertCol = 2; + if (estimateRange != null) + { + insertCol = estimateRange.Column; + } + + Range insertRange = productSheet.Columns[$"{GetColumnLetter(insertCol)}:{GetColumnLetter(insertCol + 5)}"]; + insertRange.EntireColumn.Insert(XlInsertShiftDirection.xlShiftToRight); + insertRange = productSheet.Columns[$"{GetColumnLetter(insertCol)}:{GetColumnLetter(insertCol + 5)}"]; + insertRange.ClearFormats(); + + productSheet.Cells[1, insertCol].Value2 = $"{yearNumber}-Turnover"; + + productSheet.Cells[2, insertCol].Value2 = "數量"; + productSheet.Cells[2, insertCol + 1].Value2 = "金額(含稅)"; + productSheet.Cells[2, insertCol + 2].Value2 = "金額(未稅)"; + productSheet.Cells[2, insertCol + 3].Value2 = "平均單價"; + productSheet.Cells[2, insertCol + 4].Value2 = "成長率"; + var growthRange = productSheet.Range[ + productSheet.Cells[2, insertCol + 4], + productSheet.Cells[2, insertCol + 5] + ]; + growthRange.Merge(); + + turnOverRange = productSheet.Range[ + productSheet.Cells[1, insertCol], + productSheet.Cells[1, insertCol + 5] + ]; + turnOverRange.Merge(); + } + + var productSalesList = context.SalesDetails + .Where(x => x.InvoiceDate.HasValue && x.InvoiceDate.Value.Year == yearNumber) + .GroupBy(x => x.ProductNameEN) + .Select(grouped => new ImportContext.GroupedSalesDetailModel() + { + ProductNameEN = grouped.Key, + Quantity = grouped.Sum(x => x.Quantity ?? 0), + TotalAmount = grouped.Sum(x => x.TotalAmount ?? 0), + Details = grouped.ToList() + }) + .ToList(); + + Range totalRange = usedRange.Find(What: $"總計", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); + + int totalRowCount = usedRange.Rows.Count + 1; + if (totalRange != null) + { + totalRowCount = totalRange.Row; + } + + int startColIndex = turnOverRange.Column; + var quantityColLetter = GetColumnLetter(startColIndex); + var amountColLetter = GetColumnLetter(startColIndex + 1); + var amountNoTaxColLetter = GetColumnLetter(startColIndex + 2); + var prevQuantityColLetter = GetColumnLetter(startColIndex + 6); + var prevAmountColLetter = GetColumnLetter(startColIndex + 7); + var prevAmountNoTaxColLetter = GetColumnLetter(startColIndex + 8); + + foreach (var productSalesSummary in productSalesList) + { + var curProductName = productSalesSummary.ProductNameEN; + // look for the product row + // if not exists then create a new row + Range productRange = usedRange.Find( + What: curProductName, + LookIn: XlFindLookIn.xlValues, + LookAt: XlLookAt.xlWhole, + SearchOrder: XlSearchOrder.xlByRows, + SearchDirection: XlSearchDirection.xlNext, + MatchCase: false + ); + + if (productRange == null) + { + productSheet.Rows[totalRowCount].Insert(); + productSheet.Cells[totalRowCount, 1].Value2 = curProductName; + productRange = productSheet.Cells[totalRowCount, 1]; + + totalRowCount += 1; + } + + int curRowIndex = productRange.Row; + + productSheet.Cells[curRowIndex, startColIndex].Value2 = productSalesSummary.Quantity; + productSheet.Cells[curRowIndex, startColIndex + 1].Value2 = productSalesSummary.TotalAmount; + productSheet.Cells[curRowIndex, startColIndex + 2].formula = $"={amountColLetter}{curRowIndex}/1.13"; + productSheet.Cells[curRowIndex, startColIndex + 3].formula = $"={amountColLetter}{curRowIndex}/${quantityColLetter}{curRowIndex}"; + + productSheet.Cells[curRowIndex, startColIndex + 4].formula = $"={quantityColLetter}{curRowIndex}/${prevQuantityColLetter}{curRowIndex}"; + productSheet.Cells[curRowIndex, startColIndex + 5].formula = $"={amountColLetter}{curRowIndex}/${prevAmountColLetter}{curRowIndex}"; + + productSheet.Cells[curRowIndex, startColIndex + 4].NumberFormat = "0.00%"; + productSheet.Cells[curRowIndex, startColIndex + 5].NumberFormat = "0.00%"; + } + + productSheet.Cells[totalRowCount, 1].Value2 = "總計"; + productSheet.Cells[totalRowCount, startColIndex].formula = $"=SUM({quantityColLetter}5:{quantityColLetter}{totalRowCount - 1})"; + productSheet.Cells[totalRowCount, startColIndex + 1].formula = $"=SUM({amountColLetter}5:{amountColLetter}{totalRowCount - 1})"; + productSheet.Cells[totalRowCount, startColIndex + 2].formula = $"=SUM({amountNoTaxColLetter}5:{amountNoTaxColLetter}{totalRowCount - 1})"; + + productSheet.Cells[totalRowCount + 1, 1].Value2 = "備註"; + productSheet.Cells[totalRowCount + 1, startColIndex].formula = $"={quantityColLetter}{totalRowCount}/{prevQuantityColLetter}{totalRowCount}"; + productSheet.Cells[totalRowCount + 1, startColIndex + 1].formula = $"={amountColLetter}{totalRowCount}/{prevAmountColLetter}{totalRowCount}"; + productSheet.Cells[totalRowCount + 1, startColIndex + 2].formula = $"={amountNoTaxColLetter}{totalRowCount}/{prevAmountNoTaxColLetter}{totalRowCount}"; + + productSheet.Cells[totalRowCount + 1, startColIndex].NumberFormat = "0.00%"; + productSheet.Cells[totalRowCount + 1, startColIndex + 1].NumberFormat = "0.00%"; + productSheet.Cells[totalRowCount + 1, startColIndex + 2].NumberFormat = "0.00%"; + }); + + FormatSheetStyle(productSheet); + } + + public static void GenDepartmentSummary(GenForecaseContext context) + { + var app = context.Application; + + string sheetName = $"部门统计"; + Worksheet deptSheet = CreateSheetIfNotExisted(app, sheetName); + deptSheet.Activate(); + + deptSheet.Cells[1, 1].Value2 = "鈞全"; + deptSheet.Range[deptSheet.Cells[1, 1], deptSheet.Cells[2, 1]].Merge(); + + var yearList = context.SalesForecastList + .Select(x => x.Year) + .Distinct() + .ToList(); + + Range initUsedRange = deptSheet.UsedRange; + + Range totalRange = initUsedRange.Find(What: $"Total", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); + + int totalRowIndex = initUsedRange.Rows.Count + 1; + if (totalRange != null) + { + totalRowIndex = totalRange.Row; + } + + yearList.ForEach(yearNumber => + { + Range curYearRange = deptSheet.UsedRange.Find(What: $"{yearNumber}", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); + + int startCol = deptSheet.UsedRange.Column + 1; + if (curYearRange != null) + { + startCol = curYearRange.Column; + } + + var startColLetter = GetColumnLetter(startCol); + var endColLetter = GetColumnLetter(startCol + 3); + + Range prevYearRange = deptSheet.UsedRange.Find(What: $"{yearNumber - 1}", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); + int prevStartCol = 0; + string prevForecastColLetter = ""; + string prevTurnoverColLetter = ""; + if (prevYearRange != null) + { + prevStartCol = prevYearRange.Column; + prevForecastColLetter = GetColumnLetter(prevStartCol); + prevTurnoverColLetter = GetColumnLetter(prevStartCol + 1); + } + + deptSheet.Range[$"{startColLetter}:{endColLetter}"] + .EntireColumn + .Insert(XlInsertShiftDirection.xlShiftToRight); + + deptSheet.Cells[1, 2].Value2 = $"{yearNumber}"; + deptSheet.Range[deptSheet.Cells[1, startCol], deptSheet.Cells[1, startCol + 3]].Merge(); + + deptSheet.Cells[2, startCol].Value2 = "Forecast"; + deptSheet.Cells[2, startCol + 1].Value2 = "Turnover"; + deptSheet.Cells[2, startCol + 2].Value2 = "成長率"; + deptSheet.Cells[2, startCol + 3].Value2 = "達成率"; + + var forcastColLetter = startColLetter; + var turnoverColLetter = GetColumnLetter(startCol + 1); + foreach (var salesForecast in context.SalesForecastList.Where(x => x.Name.ToUpper() != "TOTAL")) + { + var salesName = salesForecast.Name; + Range curUsedRange = deptSheet.UsedRange; + + Range salesNameRange = curUsedRange.Find(What: $"{salesName}", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); + + if (salesNameRange == null) + { + deptSheet.Rows[totalRowIndex].Insert(); + deptSheet.Cells[curUsedRange.Rows.Count + 1, 1].Value2 = salesName; + + salesNameRange = deptSheet.Cells[curUsedRange.Rows.Count + 1, 1]; + totalRowIndex++; + } + + deptSheet.Cells[salesNameRange.Row, startCol].Value2 = salesForecast.Total; + if (prevYearRange != null) + { + deptSheet.Cells[salesNameRange.Row, startCol + 2].formula = $"=SUM({forcastColLetter}{salesNameRange.Row} - {prevTurnoverColLetter}{salesNameRange.Row})/{forcastColLetter}{salesNameRange.Row}"; + deptSheet.Cells[salesNameRange.Row, startCol + 2].NumberFormat = "0.00%"; + } + deptSheet.Cells[salesNameRange.Row, startCol + 3].formula = $"={turnoverColLetter}{salesNameRange.Row}/{forcastColLetter}{salesNameRange.Row}"; + deptSheet.Cells[salesNameRange.Row, startCol + 3].NumberFormat = "0.00%"; + } + + deptSheet.Cells[totalRowIndex, 1].Value2 = "Total"; + deptSheet.Cells[totalRowIndex, startCol].formula = $"=SUM({forcastColLetter}3:{forcastColLetter}{totalRowIndex - 1})"; + deptSheet.Cells[totalRowIndex, startCol + 1].formula = $"=SUM({turnoverColLetter}3:{turnoverColLetter}{totalRowIndex - 1})"; + + if (prevYearRange != null) + { + deptSheet.Cells[totalRowIndex, startCol + 2].formula = $"=SUM({forcastColLetter}{totalRowIndex} - {prevTurnoverColLetter}{totalRowIndex})/{forcastColLetter}{totalRowIndex}"; + deptSheet.Cells[totalRowIndex, startCol + 2].NumberFormat = "0.00%"; + } + deptSheet.Cells[totalRowIndex, startCol + 3].formula = $"={turnoverColLetter}{totalRowIndex}/{forcastColLetter}{totalRowIndex}"; + deptSheet.Cells[totalRowIndex, startCol + 3].NumberFormat = "0.00%"; + }); + + FormatSheetStyle(deptSheet); + } + + public static void UpdateDeptartmentSummary(ImportContext context) + { + var app = context.Application; + + string sheetName = $"部门统计"; + Worksheet deptSheet = CreateSheetIfNotExisted(app, sheetName); + deptSheet.Activate(); + + var yearList = context.SalesDetails.Where(x => x.InvoiceDate.HasValue).Select(x => x.InvoiceDate.Value.Year).Distinct().ToList(); + + yearList.ForEach(yearoNumber => + { + Range yearRange = deptSheet.UsedRange.Find(What: $"{yearoNumber}", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); + + if (yearRange == null) + { + Console.WriteLine("Cannot find year column in department summary: {0}", yearoNumber); + return; + } + + var startCol = yearRange.Column; + var startColLetter = GetColumnLetter(startCol); + var turnoverColLetter = GetColumnLetter(startCol + 1); + + Range totalRange = deptSheet.UsedRange.Find(What: $"Total", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); + int totalRowIndex = deptSheet.UsedRange.Rows.Count + 1; + if (totalRange != null) + { + totalRowIndex = totalRange.Row; + } + + var salesDetails = context.SalesDetails + .Where(x => x.InvoiceDate.HasValue && x.InvoiceDate.Value.Year == yearoNumber && x.SalesName.ToUpper() != "TOTAL") + .GroupBy(x => x.SalesName) + .Select(grouped => new + { + SalesName = grouped.Key, + Turnover = grouped.Sum(x => x.TotalAmount ?? 0) + }) + .ToList(); + + foreach (var salesDetail in salesDetails) + { + var salesName = salesDetail.SalesName; + Range curUsedRange = deptSheet.UsedRange; + + Range salesNameRange = curUsedRange.Find(What: $"{salesName}", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); + + if (salesNameRange != null) + { + deptSheet.Cells[salesNameRange.Row, startCol + 1].Value2 = salesDetail.Turnover; + } + } + + deptSheet.Cells[totalRowIndex, startCol + 1].formula = $"=SUM({turnoverColLetter}3:{turnoverColLetter}{totalRowIndex - 1})"; + }); + } } }