diff --git a/KellyReport_D.csproj b/KellyReport_D.csproj index 32343c0..7b147fc 100644 --- a/KellyReport_D.csproj +++ b/KellyReport_D.csproj @@ -34,7 +34,7 @@ publish\ zh-chs - 1.0.0.4 + 1.0.0.7 true true 7 @@ -227,6 +227,7 @@ Code + JQSalesSummaryPanel.cs diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index 9489fc0..54a36a3 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -34,5 +34,5 @@ using System.Security; // 方法是按如下所示使用“*”: : // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.6")] diff --git a/Ribbon1.cs b/Ribbon1.cs index ba7c104..c0219a1 100644 --- a/Ribbon1.cs +++ b/Ribbon1.cs @@ -3,7 +3,9 @@ using Microsoft.Office.Tools; using Microsoft.Office.Tools.Ribbon; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Reflection; using System.Text; namespace KellyReport_D @@ -12,6 +14,20 @@ namespace KellyReport_D { private CustomTaskPane jqSalesSummaryPanel; + private string GetAppVersion() + { + var asm = Assembly.GetExecutingAssembly(); + try + { + var fvi = FileVersionInfo.GetVersionInfo(asm.Location); + if (!string.IsNullOrWhiteSpace(fvi.FileVersion)) + return fvi.FileVersion; + } + catch { } + + return asm.GetName().Version.ToString(); + } + private void Ribbon1_Load(object sender, RibbonUIEventArgs e) { this.button1.Label = Resources.APP_NAME; @@ -23,7 +39,7 @@ namespace KellyReport_D if (jqSalesSummaryPanel == null) { var panel = new JQSalesSummaryPanel(); - jqSalesSummaryPanel = Globals.ThisAddIn.CustomTaskPanes.Add(panel, Resources.APP_NAME); + jqSalesSummaryPanel = Globals.ThisAddIn.CustomTaskPanes.Add(panel, $"{Resources.APP_NAME} v{GetAppVersion()}"); jqSalesSummaryPanel.Width = 600; } diff --git a/Utils/FileLogger.cs b/Utils/FileLogger.cs new file mode 100644 index 0000000..3ccb624 --- /dev/null +++ b/Utils/FileLogger.cs @@ -0,0 +1,47 @@ +using System; +using System.IO; +using System.Text; + +namespace KellyReport_D.Utils +{ + internal static class FileLogger + { + private static readonly object _sync = new object(); + private static string LogDirectory => + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "KellyReport", "logs"); + + private static string GetLogFilePath() + { + string fileName = $"kellyreport_{DateTime.Now:yyyyMMdd}.log"; + return Path.Combine(LogDirectory, fileName); + } + + private static void WriteInternal(string level, string message, Exception ex = null) + { + try + { + lock (_sync) + { + Directory.CreateDirectory(LogDirectory); + var path = GetLogFilePath(); + using (var sw = new StreamWriter(path, true, Encoding.UTF8)) + { + sw.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} [{level}] {message}"); + if (ex != null) + { + sw.WriteLine(ex.ToString()); + } + } + } + } + catch + { + // 日志写入不应影响主流程,忽略任何异常 + } + } + + public static void Info(string message) => WriteInternal("INFO", message); + public static void Debug(string message) => WriteInternal("DEBUG", message); + public static void Error(string message, Exception ex = null) => WriteInternal("ERROR", message, ex); + } +} diff --git a/Utils/WorkBookUtils.cs b/Utils/WorkBookUtils.cs index 84a7a22..84a62c9 100644 --- a/Utils/WorkBookUtils.cs +++ b/Utils/WorkBookUtils.cs @@ -5,8 +5,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; -using System.Threading.Tasks; using KellyReport_D.Model; +using System.Runtime.InteropServices; namespace KellyReport_D.Utils { @@ -40,7 +40,10 @@ namespace KellyReport_D.Utils UpdateEstimatedTable(importContext); UpdateDeptartmentSummary(importContext); + // 产品年度销量统计 GenProductSummarySheet(importContext); + // 生成客户年度销量统计 + GenClientSummarySheet(importContext); } private static void UpdateEstimatedTable(ImportContext importContext) @@ -66,9 +69,12 @@ namespace KellyReport_D.Utils Range usedRange = bussenessWorksheet.UsedRange; int maxColIndex = usedRange.Columns.Count; + int yearRowIndex = 1; + FindBussenessYearRow(bussenessWorksheet, importContext.YearList[0].ToString(), ref yearRowIndex); + for (int i = 2; i < maxColIndex; i += 4) { - String salesName = bussenessWorksheet.Cells[1, i].Value2; + String salesName = bussenessWorksheet.Cells[yearRowIndex, i].Value2; if (string.IsNullOrWhiteSpace(salesName)) { continue; @@ -90,7 +96,7 @@ namespace KellyReport_D.Utils .ToList() .ForEach(monthlySales => { - int startRowIndex = 3; + int startRowIndex = yearRowIndex + 2; for (int rowIndex = startRowIndex; rowIndex < startRowIndex + 12; rowIndex++) { var monthCell = bussenessWorksheet.Cells[rowIndex, 1]; @@ -214,7 +220,7 @@ namespace KellyReport_D.Utils } catch (Exception ex) { - Console.Error.WriteLine("delete error %s", ex.Message); + FileLogger.Error("删除Total(All)旧数据时发生错误", ex); } finally { @@ -381,7 +387,7 @@ namespace KellyReport_D.Utils { for (var i = 0; i < clientSalesRowCount - curRowCount; i++) { - userSheet.Rows[clientSalesRowIndex + curRowCount].Insert(XlInsertShiftDirection.xlShiftDown); + userSheet.Rows[clientSalesRowIndex + curRowCount - 1].Insert(XlInsertShiftDirection.xlShiftDown); summaryRowIndex++; } } @@ -455,19 +461,61 @@ namespace KellyReport_D.Utils private static int WriteSalesDetailToSheet(Worksheet userSheet, int rowIndex, int yearColIndex, ClientContact clientContact, IEnumerable salesDetailList) { + Range productNamesCellRange = userSheet.Range[ + userSheet.Cells[rowIndex, 11], + userSheet.Cells[rowIndex + salesDetailList.Count(), 11] + ]; + foreach (var productSalesDetail in salesDetailList) { + int currentRowIndex = rowIndex; + // 如果存在相同产品名称,则写入对应行 + if (productSalesDetail.ProductNameEN != "總銷售額") + { + + var targetProductCell = productNamesCellRange.Find(What: productSalesDetail.ProductNameEN, LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); + if (targetProductCell != null) + { + currentRowIndex = targetProductCell.Row; + } + else + { + Range blankCell = null; + try + { + + blankCell = productNamesCellRange.SpecialCells(XlCellType.xlCellTypeBlanks); + } + catch (COMException) + { + // 没有空白单元格 + //productNamesCellRange.Insert(XlInsertShiftDirection.xlShiftDown); + MessageBox.Show($"写入数据时发生错误,可能是因为产品名称过多导致,错误数据:{productSalesDetail.SalesName}-{productSalesDetail.ClientName}-{productSalesDetail.ProductNameEN}。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + rowIndex++; + + continue; + //userSheet.Rows[rowIndex + salesDetailList.Count() - 1].Insert(XlInsertShiftDirection.xlShiftDown); + //blankCell = userSheet.Cells[rowIndex + salesDetailList.Count() - 1, 11]; + } + + if (blankCell != null) + { + currentRowIndex = blankCell.Row; + } + } + } + //userSheet.Cells[rowIndex, 1].Value2 = productSalesDetail.SalesName; - userSheet.Cells[rowIndex, 2].Value2 = clientContact.Region; - userSheet.Cells[rowIndex, 3].Value2 = clientContact.Industry; - userSheet.Cells[rowIndex, 4].Value2 = clientContact.AnnualCapacity; - userSheet.Cells[rowIndex, 5].Value2 = clientContact.Name; - userSheet.Cells[rowIndex, 6].Value2 = clientContact.PurchasingManager; - userSheet.Cells[rowIndex, 7].Value2 = clientContact.PurchasingManagerPhone; - userSheet.Cells[rowIndex, 8].Value2 = clientContact.Purchaser; - userSheet.Cells[rowIndex, 9].Value2 = clientContact.PurchaserPhone; - userSheet.Cells[rowIndex, 10].Value2 = clientContact.Address; - userSheet.Cells[rowIndex, 11].Value2 = productSalesDetail.ProductNameEN; + userSheet.Cells[currentRowIndex, 2].Value2 = clientContact.Region; + userSheet.Cells[currentRowIndex, 3].Value2 = clientContact.Industry; + userSheet.Cells[currentRowIndex, 4].Value2 = clientContact.AnnualCapacity; + userSheet.Cells[currentRowIndex, 5].Value2 = clientContact.Name; + userSheet.Cells[currentRowIndex, 6].Value2 = clientContact.PurchasingManager; + userSheet.Cells[currentRowIndex, 7].Value2 = clientContact.PurchasingManagerPhone; + userSheet.Cells[currentRowIndex, 8].Value2 = clientContact.Purchaser; + userSheet.Cells[currentRowIndex, 9].Value2 = clientContact.PurchaserPhone; + userSheet.Cells[currentRowIndex, 10].Value2 = clientContact.Address; + userSheet.Cells[currentRowIndex, 11].Value2 = productSalesDetail.ProductNameEN; // 计算每月的数量和金额 int monthCol = yearColIndex; @@ -500,19 +548,19 @@ namespace KellyReport_D.Utils if (monthDetail == null) { - userSheet.Cells[rowIndex, monthCol + (i - 1) * 3].Value2 = 0; // 單價 - userSheet.Cells[rowIndex, monthCol + (i - 1) * 3 + 1].Value2 = 0; // 數量 - userSheet.Cells[rowIndex, monthCol + (i - 1) * 3 + 2].Value2 = 0; // 金額 + userSheet.Cells[currentRowIndex, monthCol + (i - 1) * 3].Value2 = 0; // 單價 + userSheet.Cells[currentRowIndex, monthCol + (i - 1) * 3 + 1].Value2 = 0; // 數量 + userSheet.Cells[currentRowIndex, monthCol + (i - 1) * 3 + 2].Value2 = 0; // 金額 } else { - userSheet.Cells[rowIndex, monthCol + (i - 1) * 3].Value2 = monthDetail.PriceWithTax ?? 0; // 單價 - userSheet.Cells[rowIndex, monthCol + (i - 1) * 3 + 1].Value2 = monthDetail.Quantity ?? 0; // 數量 - userSheet.Cells[rowIndex, monthCol + (i - 1) * 3 + 2].Value2 = monthDetail.TotalAmount ?? 0; // 总额 + userSheet.Cells[currentRowIndex, monthCol + (i - 1) * 3].Value2 = monthDetail.PriceWithTax ?? 0; // 單價 + userSheet.Cells[currentRowIndex, monthCol + (i - 1) * 3 + 1].Value2 = monthDetail.Quantity ?? 0; // 數量 + userSheet.Cells[currentRowIndex, monthCol + (i - 1) * 3 + 2].Value2 = monthDetail.TotalAmount ?? 0; // 总额 } } - userSheet.Cells[rowIndex, 49].Value = productSalesDetail.Quantity; - userSheet.Cells[rowIndex, 50].Value = productSalesDetail.TotalAmount; + userSheet.Cells[currentRowIndex, 49].Value = productSalesDetail.Quantity; + userSheet.Cells[currentRowIndex, 50].Value = productSalesDetail.TotalAmount; rowIndex++; } @@ -656,7 +704,7 @@ namespace KellyReport_D.Utils } catch (Exception ex) { - Console.Error.WriteLine("parse date error:" + ex.Message); + FileLogger.Error($"解析日期错误 '{rawValue}'", ex); //throw new FormatException($"日期格式错误 '{rawValue}'"); } @@ -741,28 +789,25 @@ namespace KellyReport_D.Utils // 获取「銷售年度預估」工作表 ReadSalesForecastData(context); // 获取「產品年度預估」工作表 - //ReadProductForecastData(context); + // GenProductForecastData(context); GenDepartmentSummary(context); GenBussenessSheet(context); } catch (Exception ex) { - Console.WriteLine("Error generating forecast:" + ex.Message); + FileLogger.Error("生成销售年度预估表失败", ex); + MessageBox.Show("分析预估表失败: " + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } button.Text = originLabel; } - public static void GenBussenessSheet(GenForecaseContext context) + public static bool FindBussenessYearRow(Worksheet sheet, string targetYear, ref int yearRowIndex) { - var app = context.Application; - Worksheet sheet = CreateSheetIfNotExisted(app, "業務月達成率"); - Range usedRange = sheet.UsedRange; int totalRows = usedRange?.Rows?.Count ?? 0; - int yearRowIndex = 1; Boolean updateCurrentBook = false; while (yearRowIndex < totalRows - 1) { @@ -777,7 +822,7 @@ namespace KellyReport_D.Utils yearValue = (rawYearValue).ToString(); } - if (yearValue == context.MonthData[0]) + if (yearValue == targetYear) { updateCurrentBook = true; break; @@ -786,6 +831,19 @@ namespace KellyReport_D.Utils yearRowIndex += 15; } + return updateCurrentBook; + } + + public static void GenBussenessSheet(GenForecaseContext context) + { + var app = context.Application; + Worksheet sheet = CreateSheetIfNotExisted(app, "業務月達成率"); + + Range usedRange = sheet.UsedRange; + int totalRows = usedRange?.Rows?.Count ?? 0; + int yearRowIndex = 1; + Boolean updateCurrentBook = FindBussenessYearRow(sheet, context.MonthData[0], ref yearRowIndex); + try { string rowRanges = ""; @@ -808,6 +866,7 @@ namespace KellyReport_D.Utils } catch (Exception ex) { + FileLogger.Error($"更新{context.MonthData[0]}年業務月達成率失败", ex); throw new Exception($"更新{context.MonthData[0]}年業務月達成率失败", ex); } @@ -894,6 +953,8 @@ namespace KellyReport_D.Utils } catch (Exception ex) { + FileLogger.Error($"写入{context.MonthData[0]}年業務月達成率失败", ex); + MessageBox.Show("写入业绩月达成率时发生错误:" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } @@ -934,44 +995,6 @@ namespace KellyReport_D.Utils context.ClientContactList = clientContactList; } - private static void ReadProductForecastData(GenForecaseContext context) - { - var app = context.Application; - - var productForecastSheet = app.Worksheets["產品年度預估"] as Worksheet; - var productRange = productForecastSheet.UsedRange; - IList productForecastList = new List(); - - if (productRange.Value2 is object[,] rawProductData) - { - int rowCount = rawProductData.GetLength(0); - int colCount = rawProductData.GetLength(1); - if (rawProductData[1, 2] == null) - { - throw new MissingMemberException("「產品年度預估」工作表的(年份)不能为空,请检查数据格式。"); - } - var year = Convert.ToInt32(rawProductData[1, 2]); - - // 模板第三行开始是数据 - for (int i = 3; i <= rowCount; i++) - { - var input = new ProductForecastInput - { - Year = year, - Name = rawProductData[i, 1]?.ToString() ?? "", - Quantity = rawProductData[i, 2] != null ? Convert.ToDecimal(rawProductData[i, 2]) : 0, - AmountTax = rawProductData[i, 3] != null ? Convert.ToDecimal(rawProductData[i, 3]) : 0, - Amount = rawProductData[i, 4] != null ? Convert.ToDecimal(rawProductData[i, 4]) : 0, - PriceAvg = rawProductData[i, 5] != null ? Convert.ToDecimal(rawProductData[i, 5]) : 0, - }; - productForecastList.Add(input); - } - - } - - context.ProductForecastList = productForecastList; - } - private static void ReadSalesForecastData(GenForecaseContext context) { var app = context.Application; @@ -1076,6 +1099,7 @@ namespace KellyReport_D.Utils ]; turnOverRange.Merge(); } + usedRange = productSheet.UsedRange; var productSalesList = context.SalesDetails .Where(x => x.InvoiceDate.HasValue && x.InvoiceDate.Value.Year == yearNumber) @@ -1089,9 +1113,9 @@ namespace KellyReport_D.Utils }) .ToList(); - Range totalRange = usedRange.Find(What: $"總計", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); + Range totalRange = productSheet.UsedRange.Find(What: $"總計", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); - int totalRowCount = usedRange.Rows.Count + 1; + int totalRowCount = productSheet.UsedRange.Rows.Count + 1; if (totalRange != null) { totalRowCount = totalRange.Row; @@ -1110,7 +1134,7 @@ namespace KellyReport_D.Utils var curProductName = productSalesSummary.ProductNameEN; // look for the product row // if not exists then create a new row - Range productRange = usedRange.Find( + Range productRange = productSheet.UsedRange.Find( What: curProductName, LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, @@ -1159,6 +1183,127 @@ namespace KellyReport_D.Utils FormatSheetStyle(productSheet); } + public static void GenClientSummarySheet(ImportContext context) + { + var app = context.Application; + + string sheetName = $"客戶年度預估"; + Worksheet clientSheet = CreateSheetIfNotExisted(app, sheetName); + clientSheet.Activate(); + + context.YearList.ForEach(yearNumber => + { + Range turnOverRange = clientSheet.UsedRange.Find(What: $"{yearNumber}-Turnover", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); + var clientSalesList = context.SalesDetails + .Where(x => x.InvoiceDate.HasValue && x.InvoiceDate.Value.Year == yearNumber) + .GroupBy(x => x.ClientName) + .Select(grouped => new ImportContext.GroupedSalesDetailModel() + { + ClientName = grouped.Key, + Quantity = grouped.Sum(x => x.Quantity ?? 0), + TotalAmount = grouped.Sum(x => x.TotalAmount ?? 0), + Details = grouped.ToList() + }) + .ToList(); + if (turnOverRange == null) + { + int insertCol = 2; + + Range insertRange = clientSheet.Columns[$"{GetColumnLetter(insertCol)}:{GetColumnLetter(insertCol + 5)}"]; + insertRange.EntireColumn.Insert(XlInsertShiftDirection.xlShiftToRight); + insertRange = clientSheet.Columns[$"{GetColumnLetter(insertCol)}:{GetColumnLetter(insertCol + 5)}"]; + insertRange.ClearFormats(); + + clientSheet.Cells[1, insertCol].Value2 = $"{yearNumber}-Turnover"; + + clientSheet.Cells[2, insertCol].Value2 = "數量"; + clientSheet.Cells[2, insertCol + 1].Value2 = "金額(含稅)"; + clientSheet.Cells[2, insertCol + 2].Value2 = "金額(未稅)"; + clientSheet.Cells[2, insertCol + 3].Value2 = "平均單價"; + clientSheet.Cells[2, insertCol + 4].Value2 = "成長率"; + var growthRange = clientSheet.Range[ + clientSheet.Cells[2, insertCol + 4], + clientSheet.Cells[2, insertCol + 5] + ]; + growthRange.Merge(); + + turnOverRange = clientSheet.Range[ + clientSheet.Cells[1, insertCol], + clientSheet.Cells[1, insertCol + 5] + ]; + turnOverRange.Merge(); + } + + Range totalRange = clientSheet.UsedRange.Find(What: $"總計", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); + + int totalRowCount = clientSheet.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 clientSalesGroup in clientSalesList) + { + var curClientName = clientSalesGroup.ClientName; + // look for the Client row + // if not exists then create a new row + Range clientRange = clientSheet.UsedRange.Find( + What: curClientName, + LookIn: XlFindLookIn.xlValues, + LookAt: XlLookAt.xlWhole, + SearchOrder: XlSearchOrder.xlByRows, + SearchDirection: XlSearchDirection.xlNext, + MatchCase: false + ); + + if (clientRange == null) + { + clientSheet.Rows[totalRowCount].Insert(); + clientSheet.Cells[totalRowCount, 1].Value2 = curClientName; + clientRange = clientSheet.Cells[totalRowCount, 1]; + + totalRowCount += 1; + } + + int curRowIndex = clientRange.Row; + + clientSheet.Cells[curRowIndex, startColIndex].Value2 = clientSalesGroup.Quantity; + clientSheet.Cells[curRowIndex, startColIndex + 1].Value2 = clientSalesGroup.TotalAmount; + clientSheet.Cells[curRowIndex, startColIndex + 2].formula = $"={amountColLetter}{curRowIndex}/1.13"; + clientSheet.Cells[curRowIndex, startColIndex + 3].formula = $"={amountColLetter}{curRowIndex}/${quantityColLetter}{curRowIndex}"; + + clientSheet.Cells[curRowIndex, startColIndex + 4].formula = $"={quantityColLetter}{curRowIndex}/${prevQuantityColLetter}{curRowIndex}"; + clientSheet.Cells[curRowIndex, startColIndex + 5].formula = $"={amountColLetter}{curRowIndex}/${prevAmountColLetter}{curRowIndex}"; + + clientSheet.Cells[curRowIndex, startColIndex + 4].NumberFormat = "0.00%"; + clientSheet.Cells[curRowIndex, startColIndex + 5].NumberFormat = "0.00%"; + } + + clientSheet.Cells[totalRowCount, 1].Value2 = "總計"; + clientSheet.Cells[totalRowCount, startColIndex].formula = $"=SUM({quantityColLetter}5:{quantityColLetter}{totalRowCount - 1})"; + clientSheet.Cells[totalRowCount, startColIndex + 1].formula = $"=SUM({amountColLetter}5:{amountColLetter}{totalRowCount - 1})"; + clientSheet.Cells[totalRowCount, startColIndex + 2].formula = $"=SUM({amountNoTaxColLetter}5:{amountNoTaxColLetter}{totalRowCount - 1})"; + + clientSheet.Cells[totalRowCount + 1, 1].Value2 = "備註"; + clientSheet.Cells[totalRowCount + 1, startColIndex].formula = $"={quantityColLetter}{totalRowCount}/{prevQuantityColLetter}{totalRowCount}"; + clientSheet.Cells[totalRowCount + 1, startColIndex + 1].formula = $"={amountColLetter}{totalRowCount}/{prevAmountColLetter}{totalRowCount}"; + clientSheet.Cells[totalRowCount + 1, startColIndex + 2].formula = $"={amountNoTaxColLetter}{totalRowCount}/{prevAmountNoTaxColLetter}{totalRowCount}"; + + clientSheet.Cells[totalRowCount + 1, startColIndex].NumberFormat = "0.00%"; + clientSheet.Cells[totalRowCount + 1, startColIndex + 1].NumberFormat = "0.00%"; + clientSheet.Cells[totalRowCount + 1, startColIndex + 2].NumberFormat = "0.00%"; + }); + + FormatSheetStyle(clientSheet); + } public static void GenDepartmentSummary(GenForecaseContext context) { @@ -1171,10 +1316,7 @@ namespace KellyReport_D.Utils 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(); + var yearList = context.SalesForecastList.Select(x => x.Year).Distinct().ToList(); Range initUsedRange = deptSheet.UsedRange; @@ -1269,6 +1411,133 @@ namespace KellyReport_D.Utils FormatSheetStyle(deptSheet); } +/* public static void GenProductForecastData(GenForecaseContext context) + { + var app = context.Application; + + string sheetName = $"產品年度預估"; + Worksheet productSheet = CreateSheetIfNotExisted(app, sheetName); + productSheet.Activate(); + + var yearList = context.SalesForecastList.Select(x => x.Year).Distinct().ToList(); + string yearNumber = yearList[0].ToString(); + + Range estimateRange = productSheet.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}-Estimate"; + + 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(); + + estimateRange = productSheet.Range[ + productSheet.Cells[1, insertCol], + productSheet.Cells[1, insertCol + 5] + ]; + estimateRange.Merge(); + + var productSalesList = context.SalesForecastList + .Where(x => x..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 = clientSheet.UsedRange.Find(What: $"總計", LookIn: XlFindLookIn.xlValues, LookAt: XlLookAt.xlWhole, SearchOrder: XlSearchOrder.xlByRows, SearchDirection: XlSearchDirection.xlNext, MatchCase: false); + + int totalRowCount = clientSheet.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 = clientSheet.UsedRange.Find( + What: curProductName, + LookIn: XlFindLookIn.xlValues, + LookAt: XlLookAt.xlWhole, + SearchOrder: XlSearchOrder.xlByRows, + SearchDirection: XlSearchDirection.xlNext, + MatchCase: false + ); + + if (productRange == null) + { + clientSheet.Rows[totalRowCount].Insert(); + clientSheet.Cells[totalRowCount, 1].Value2 = curProductName; + productRange = clientSheet.Cells[totalRowCount, 1]; + + totalRowCount += 1; + } + + int curRowIndex = productRange.Row; + + clientSheet.Cells[curRowIndex, startColIndex].Value2 = productSalesSummary.Quantity; + clientSheet.Cells[curRowIndex, startColIndex + 1].Value2 = productSalesSummary.TotalAmount; + clientSheet.Cells[curRowIndex, startColIndex + 2].formula = $"={amountColLetter}{curRowIndex}/1.13"; + clientSheet.Cells[curRowIndex, startColIndex + 3].formula = $"={amountColLetter}{curRowIndex}/${quantityColLetter}{curRowIndex}"; + + clientSheet.Cells[curRowIndex, startColIndex + 4].formula = $"={quantityColLetter}{curRowIndex}/${prevQuantityColLetter}{curRowIndex}"; + clientSheet.Cells[curRowIndex, startColIndex + 5].formula = $"={amountColLetter}{curRowIndex}/${prevAmountColLetter}{curRowIndex}"; + + clientSheet.Cells[curRowIndex, startColIndex + 4].NumberFormat = "0.00%"; + clientSheet.Cells[curRowIndex, startColIndex + 5].NumberFormat = "0.00%"; + } + + clientSheet.Cells[totalRowCount, 1].Value2 = "總計"; + clientSheet.Cells[totalRowCount, startColIndex].formula = $"=SUM({quantityColLetter}5:{quantityColLetter}{totalRowCount - 1})"; + clientSheet.Cells[totalRowCount, startColIndex + 1].formula = $"=SUM({amountColLetter}5:{amountColLetter}{totalRowCount - 1})"; + clientSheet.Cells[totalRowCount, startColIndex + 2].formula = $"=SUM({amountNoTaxColLetter}5:{amountNoTaxColLetter}{totalRowCount - 1})"; + + clientSheet.Cells[totalRowCount + 1, 1].Value2 = "備註"; + clientSheet.Cells[totalRowCount + 1, startColIndex].formula = $"={quantityColLetter}{totalRowCount}/{prevQuantityColLetter}{totalRowCount}"; + clientSheet.Cells[totalRowCount + 1, startColIndex + 1].formula = $"={amountColLetter}{totalRowCount}/{prevAmountColLetter}{totalRowCount}"; + clientSheet.Cells[totalRowCount + 1, startColIndex + 2].formula = $"={amountNoTaxColLetter}{totalRowCount}/{prevAmountNoTaxColLetter}{totalRowCount}"; + + clientSheet.Cells[totalRowCount + 1, startColIndex].NumberFormat = "0.00%"; + clientSheet.Cells[totalRowCount + 1, startColIndex + 1].NumberFormat = "0.00%"; + clientSheet.Cells[totalRowCount + 1, startColIndex + 2].NumberFormat = "0.00%"; + }); + FormatSheetStyle(clientSheet); + } +*/ + public static void UpdateDeptartmentSummary(ImportContext context) { var app = context.Application; @@ -1283,7 +1552,7 @@ namespace KellyReport_D.Utils if (yearRange == null) { - Console.WriteLine("Cannot find year column in department summary: {0}", yearoNumber); + FileLogger.Info($"Cannot find year column in department summary: {yearoNumber}"); return; }