From 90b4c46bf8b2e513a44de27fdaaccc18c9cb24a3 Mon Sep 17 00:00:00 2001 From: "earo.lau" Date: Sun, 10 Aug 2025 22:21:48 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=9F=E6=88=90=E5=B9=B4=E5=BA=A6=E9=94=80?= =?UTF-8?q?=E5=94=AE=E9=A2=84=E4=BC=B0=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JQSalesSummaryPanel.Designer.cs | 1 + JQSalesSummaryPanel.cs | 7 + KellyReport_D.csproj | 5 + Model/ClientContact.cs | 47 +++++ Model/GenForecaseContext.cs | 18 ++ Model/ProductForecastInput.cs | 38 ++++ Model/SalesForecastInput.cs | 98 +++++++++++ Properties/Resources.Designer.cs | 9 + Properties/Resources.resx | 3 + Utils/WorkBookUtils.cs | 287 +++++++++++++++++++++++++++++++ 10 files changed, 513 insertions(+) create mode 100644 Model/ClientContact.cs create mode 100644 Model/GenForecaseContext.cs create mode 100644 Model/ProductForecastInput.cs create mode 100644 Model/SalesForecastInput.cs create mode 100644 Utils/WorkBookUtils.cs diff --git a/JQSalesSummaryPanel.Designer.cs b/JQSalesSummaryPanel.Designer.cs index 491deca..e324326 100644 --- a/JQSalesSummaryPanel.Designer.cs +++ b/JQSalesSummaryPanel.Designer.cs @@ -117,6 +117,7 @@ this.initForecastBtn.TabIndex = 2; this.initForecastBtn.Text = "button1"; this.initForecastBtn.UseVisualStyleBackColor = true; + this.initForecastBtn.Click += new System.EventHandler(this.initForecastBtn_Click); // // flowLayoutPanel2 // diff --git a/JQSalesSummaryPanel.cs b/JQSalesSummaryPanel.cs index 7274fc9..8c30393 100644 --- a/JQSalesSummaryPanel.cs +++ b/JQSalesSummaryPanel.cs @@ -1,4 +1,5 @@ using KellyReport_D.Properties; +using KellyReport_D.Utils; using System; using System.Collections.Generic; using System.ComponentModel; @@ -53,5 +54,11 @@ namespace KellyReport_D ); step2DescLab.Size = new Size(width, textSize.Height + padding); } + + private void initForecastBtn_Click(object sender, EventArgs e) + { + WorkBookUtils.GenForecast(this.initForecastBtn); + + } } } diff --git a/KellyReport_D.csproj b/KellyReport_D.csproj index aefbbb3..575efa8 100644 --- a/KellyReport_D.csproj +++ b/KellyReport_D.csproj @@ -218,9 +218,14 @@ JQSalesSummaryPanel.cs + + + + Code + JQSalesSummaryPanel.cs diff --git a/Model/ClientContact.cs b/Model/ClientContact.cs new file mode 100644 index 0000000..c392924 --- /dev/null +++ b/Model/ClientContact.cs @@ -0,0 +1,47 @@ +public class ClientContact +{ + /// + /// 地区 + /// + public string Region { get; set; } + + /// + /// 行业 + /// + public string Industry { get; set; } + + /// + /// 产能/年 + /// + public string AnnualCapacity { get; set; } + + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 采购主管 + /// + public string PurchasingManager { get; set; } + + /// + /// 采购主管电话 + /// + public string PurchasingManagerPhone { get; set; } + + /// + /// 采购 + /// + public string Purchaser { get; set; } + + /// + /// 采购电话 + /// + public string PurchaserPhone { get; set; } + + /// + /// 地址 + /// + public string Address { get; set; } +} diff --git a/Model/GenForecaseContext.cs b/Model/GenForecaseContext.cs new file mode 100644 index 0000000..0ebb906 --- /dev/null +++ b/Model/GenForecaseContext.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KellyReport_D.Model +{ + internal class GenForecaseContext + { + public Microsoft.Office.Interop.Excel.Application Application { get; set; } + + public string[] MonthData { get; set; } + public IList SalesForecastList { get; set; } + public IList ProductForecastList { get; set; } + public IList ClientContactList { get; set; } + } +} diff --git a/Model/ProductForecastInput.cs b/Model/ProductForecastInput.cs new file mode 100644 index 0000000..b5b1bc1 --- /dev/null +++ b/Model/ProductForecastInput.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KellyReport_D.Model +{ + internal class ProductForecastInput + { + /// + /// 年份 + /// + public int Year { get; set; } + + /// + /// 产品名称 + /// + public string Name { get; set; } + + /// + /// 数量 + /// + public decimal Quantity { get; set; } + /// + /// 金额(含税) + /// + public decimal AmountTax { get; set; } + /// + /// 金额(未税) + /// + public decimal Amount { get; set; } + /// + /// 平均单价 + /// + public decimal PriceAvg { get; set; } + } +} diff --git a/Model/SalesForecastInput.cs b/Model/SalesForecastInput.cs new file mode 100644 index 0000000..587fc47 --- /dev/null +++ b/Model/SalesForecastInput.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KellyReport_D.Model +{ + internal class SalesForecastInput + { + /// + /// 年度 + /// + public int Year { get; set; } + + /// + /// 销售名称 + /// + public string Name { get; set; } + + /// + /// 总计 + /// + public decimal Total { get; set; } + + /// + /// 一月 + /// + public decimal January { get; set; } + + /// + /// 二月 + /// + public decimal February { get; set; } + + /// + /// 三月 + /// + public decimal March { get; set; } + + /// + /// 四月 + /// + public decimal April { get; set; } + + /// + /// 五月 + /// + public decimal May { get; set; } + + /// + /// 六月 + /// + public decimal June { get; set; } + + /// + /// 七月 + /// + public decimal July { get; set; } + + /// + /// 八月 + /// + public decimal August { get; set; } + + /// + /// 九月 + /// + public decimal September { get; set; } + + /// + /// 十月 + /// + public decimal October { get; set; } + + /// + /// 十一月 + /// + public decimal November { get; set; } + + /// + /// 十二月 + /// + public decimal December { get; set; } + + public List MonthlyForecast + { + get + { + return new List + { + January, February, March, April, May, June, + July, August, September, October, November, December + }; + } + } + } +} diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs index 84ae4f9..c644985 100644 --- a/Properties/Resources.Designer.cs +++ b/Properties/Resources.Designer.cs @@ -69,6 +69,15 @@ namespace KellyReport_D.Properties { } } + /// + /// 查找类似 正在生成... 的本地化字符串。 + /// + public static string GENERATING { + get { + return ResourceManager.GetString("GENERATING", resourceCulture); + } + } + /// /// 查找 System.Drawing.Bitmap 类型的本地化资源。 /// diff --git a/Properties/Resources.resx b/Properties/Resources.resx index 2f425e5..57e4eaa 100644 --- a/Properties/Resources.resx +++ b/Properties/Resources.resx @@ -120,6 +120,9 @@ JQ销售年度预估表 + + 正在生成... + ..\Resources\icon_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/Utils/WorkBookUtils.cs b/Utils/WorkBookUtils.cs new file mode 100644 index 0000000..3e7e3b8 --- /dev/null +++ b/Utils/WorkBookUtils.cs @@ -0,0 +1,287 @@ +using KellyReport_D.Properties; +using System.Windows.Forms; +using Microsoft.Office.Interop.Excel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using KellyReport_D.Model; + +namespace KellyReport_D.Utils +{ + internal static class WorkBookUtils + { + public static async void GenForecast(System.Windows.Forms.Button button) + { + String originLabel = button.Text; + if (button != null) + { + button.Text = Resources.GENERATING; + } + + var app = Globals.ThisAddIn.Application; + var context = new GenForecaseContext + { + Application = app + }; + try + { + // 获取「銷售年度預估」工作表 + readSalesForecastData(context); + // 获取「產品年度預估」工作表 + readProductForecastData(context); + // 获取「客户资料」工作表 + readClientContact(context); + + GenBussenessSheet(context); + } + catch (Exception ex) + { + Console.WriteLine("Error generating forecast:" + ex.Message); + MessageBox.Show("分析预估表失败: " + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + button.Text = originLabel; + } + + public static void GenBussenessSheet(GenForecaseContext context) + { + var app = context.Application; + + // 假设 app 是 Excel.Application + Worksheet sheet = null; + string sheetName = "業務月達成率"; + + try + { + // 新增工作表 + sheet = app.Worksheets.Add(); + sheet.Name = sheetName; + } + catch + { + // 已存在则获取 + sheet = app.Worksheets[sheetName] as Worksheet; + try + { + Range usedRange = sheet.UsedRange; + if (usedRange != null && usedRange.Cells.Count > 1) + { + usedRange.Clear(); + } + } + catch + { + // 无 UsedRange 可忽略 + } + } + + // 合并A1:A2并填入年份 + try + { + var monthData = context.MonthData; + var yearValue = monthData[0]; + Range yearRange = sheet.Range["A1", "A2"]; + yearRange.Merge(); + yearRange.Value2 = yearValue.ToString(); + yearRange.NumberFormat = "@"; // 文本格式 + + // 填入月份 + for (int i = 1; i < monthData.Length; i++) + { + sheet.Cells[i + 2, 1] = monthData[i]; + } + + // B列开始填 salesName 及相关数据 + int col = 2; // B列 + foreach (var salesForcastData in context.SalesForecastList) + { + int rowIndex = 1; // Excel 行号从1开始 + int startCol = col; + int endCol = col + 4; + + // 合并并填入 salesName + Range salesNameRange = sheet.Range[ + sheet.Cells[rowIndex, startCol], + sheet.Cells[rowIndex, startCol + 1] + ]; + salesNameRange.Merge(); + salesNameRange.Value2 = salesForcastData.Name; + + // 合并并填入“完成%” + Range finishRange = sheet.Range[ + sheet.Cells[rowIndex, startCol + 2], + sheet.Cells[rowIndex, startCol + 2] + ]; + finishRange.Merge(); + finishRange.Value2 = "完成%"; + + // 合并并填入“累積達成率” + Range accRange = sheet.Range[ + sheet.Cells[rowIndex, startCol + 3], + sheet.Cells[rowIndex, startCol + 3] + ]; + accRange.Merge(); + accRange.Value2 = "累積達成率"; + + // 第二行填入 Y-forecast 和 turnover + rowIndex++; + sheet.Cells[rowIndex, startCol] = "Y-forecast"; + sheet.Cells[rowIndex, startCol + 1] = "turnover"; + + // 填入每月数据 + rowIndex++; + foreach (var forcastMonth in salesForcastData.MonthlyForecast) + { + var cell = sheet.Cells[rowIndex, startCol]; + cell.Value2 = forcastMonth; + cell.NumberFormat = "#,##0"; + rowIndex++; + } + + col = endCol; + } + + // 自适应所有已用列宽 + Range usedRange2 = sheet.UsedRange; + if (usedRange2 != null) + { + usedRange2.Columns.AutoFit(); + } + } + catch (Exception ex) + { + MessageBox.Show("写入业绩月达成率时发生错误:" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + } + + + private static void readClientContact(GenForecaseContext context) + { + var app = context.Application; + + var clientContactSheet = app.Worksheets["客戶資料"] as Worksheet; + var clientContactRange = clientContactSheet.UsedRange; + IList clientContactList = new List(); + + if (clientContactRange.Value2 is object[,] rawContactData) + { + int rowCount = rawContactData.GetLength(0); + int colCount = rawContactData.GetLength(1); + + // 模板第二行开始是数据 + for (int i = 2; i <= rowCount; i++) + { + var input = new ClientContact + { + Region = rawContactData[i, 1]?.ToString() ?? "", + Industry = rawContactData[i, 2]?.ToString() ?? "", + AnnualCapacity = rawContactData[i, 3]?.ToString() ?? "", + Name = rawContactData[i, 4]?.ToString() ?? "", + PurchasingManager = rawContactData[i, 5]?.ToString() ?? "", + PurchasingManagerPhone = rawContactData[i, 6]?.ToString() ?? "", + Purchaser = rawContactData[i, 7]?.ToString() ?? "", + PurchaserPhone = rawContactData[i, 8]?.ToString() ?? "", + Address = rawContactData[i, 9]?.ToString() ?? "" + }; + clientContactList.Add(input); + } + } + + 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; + + var salesForecastSheet = app.Worksheets["銷售年度預估"] as Worksheet; + var salesRange = salesForecastSheet.UsedRange; + IList salesForecastInputList = new List(); + string[] monthData = new string[13]; + + if (salesRange.Value2 is object[,] rawSalesData) + { + int rowCount = rawSalesData.GetLength(0); + int colCount = rawSalesData.GetLength(1); + if (rawSalesData[1, 2] == null) + { + throw new MissingMemberException("「銷售年度預估」工作表的(年份)不能为空,请检查数据格式。"); + } + var year = Convert.ToInt32(rawSalesData[1, 2]); + monthData[0] = year.ToString(); + for (int i = 1; i <= 12; i++) + { + monthData[i] = rawSalesData[2, i + 1].ToString(); + } + + // 模板第三行开始是数据 + for (int i = 3; i <= rowCount; i++) + { + var input = new SalesForecastInput + { + Year = year, + Name = rawSalesData[i, 1]?.ToString() ?? "", + January = rawSalesData[i, 2] != null ? Convert.ToDecimal(rawSalesData[i, 2]) : 0, + February = rawSalesData[i, 3] != null ? Convert.ToDecimal(rawSalesData[i, 3]) : 0, + March = rawSalesData[i, 4] != null ? Convert.ToDecimal(rawSalesData[i, 4]) : 0, + April = rawSalesData[i, 5] != null ? Convert.ToDecimal(rawSalesData[i, 5]) : 0, + May = rawSalesData[i, 6] != null ? Convert.ToDecimal(rawSalesData[i, 6]) : 0, + June = rawSalesData[i, 7] != null ? Convert.ToDecimal(rawSalesData[i, 7]) : 0, + July = rawSalesData[i, 8] != null ? Convert.ToDecimal(rawSalesData[i, 8]) : 0, + August = rawSalesData[i, 9] != null ? Convert.ToDecimal(rawSalesData[i, 9]) : 0, + September = rawSalesData[i, 10] != null ? Convert.ToDecimal(rawSalesData[i, 10]) : 0, + October = rawSalesData[i, 11] != null ? Convert.ToDecimal(rawSalesData[i, 11]) : 0, + November = rawSalesData[i, 12] != null ? Convert.ToDecimal(rawSalesData[i, 12]) : 0, + December = rawSalesData[i, 13] != null ? Convert.ToDecimal(rawSalesData[i, 13]) : 0, + Total = rawSalesData[i, 14] != null ? Convert.ToDecimal(rawSalesData[i, 14]) : 0, + }; + salesForecastInputList.Add(input); + } + } + + context.SalesForecastList = salesForecastInputList; + context.MonthData = monthData; + } + } +}